Slide 1

Slide 1 text

Optimizing Swift Collection potatotips 49 #potatotips Ryo Aoyama - @ra1028

Slide 2

Slide 2 text

Profile - Ryo Aoyama - CyberAgent/FRESH! 2016~ - GitHub: @ra1028 - Twitter: @ra1028fe5

Slide 3

Slide 3 text

ReactiveSwift.Bag - ཁૉΛ௥Ճ͢Δ͜ͱ͕Ͱ͖Δ - ཁૉΛ௥Ճͨ͠ࡍʹฦ͞ΕͨҰҙͳKeyΛ࢖ͬͯ࡟আ͢Δ ͜ͱ͕Ͱ͖Δ - Bag: RandomAccessCollection

Slide 4

Slide 4 text

Sample public struct Bag0 { public struct Key: Equatable { fileprivate let value: UInt64 fileprivate init(value: UInt64) { self.value = value } public static func == (lhs: Key, rhs: Key) -> Bool { return lhs.value == rhs.value } } private var buffer = [(key: Key, element: Element)]() private var nextKey = Key(value: 0) public init() {} @discardableResult public mutating func add(_ element: Element) -> Key { let key = nextKey nextKey = Key(value: key.value &+ 1) buffer.append((key: key, element: element)) return key } @discardableResult public mutating func remove(for key: Key) -> Element? { if let index = buffer.indices.reversed().first(where: { buffer[$0].key == key }) { return buffer.remove(at: index).element } return nil } }

Slide 5

Slide 5 text

Sample extension Bag0: RandomAccessCollection { public var startIndex: Int { return buffer.startIndex } public var endIndex: Int { return buffer.endIndex } public func index(after i: Int) -> Int { return i + 1 } public subscript(position: Int) -> Element { return buffer[position].element } }

Slide 6

Slide 6 text

Optimization 1 ContiguousArray

Slide 7

Slide 7 text

Element͕Class͔@objcͷͱ͖ʹϝϞϦͷ࿈ଓͨ͠ྖҬʹ֨ ೲ͠ɺΠςϨʔγϣϯΛߴ଎ԽͰ͖Δ ͜ͷಛੑΛ׆͔ͨ͢ΊɺElementͱKeyͰผʑͷ഑ྻΛ֬อ public struct Bag1 { private var elements = ContiguousArray() private var keys = ContiguousArray() private var buffer = [(key: Key, element: Element)]()

Slide 8

Slide 8 text

Optimization 2 Use optimized stdlib

Slide 9

Slide 9 text

public struct Bag2 { private var elements = ContiguousArray() private var keyRawValues = ContiguousArray() private var nextKey = Key(value: 0) @discardableResult public mutating func remove(for key: Key) -> Element? { if let index = elements.indices.reversed().first(where: { keyRawValues[$0] == key.value }) { keyRawValues.remove(at: index) return elements.remove(at: index) } return nil } ࠷దԽࡁΈͷstdlibΛͦͷ··ར༻͢Δ͜ͱͰࣗલ࣮૷ΑΓ ΋ߴ଎ԽͰ͖Δ৔߹͕͋Δ Keyͷ୅ΘΓʹ࣮ମͷUInt64Λ֨ೲ͢Δ ͜ͱͰൺֱʹUInt64.==Λ࢖͏

Slide 10

Slide 10 text

Optimization 3 Custom Iterator

Slide 11

Slide 11 text

extension Bag3: RandomAccessCollection { public var startIndex: Int { return elements.startIndex } public var endIndex: Int { return elements.endIndex } public subscript(index: Int) -> Element { return elements[index] } public func makeIterator() -> ContiguousArray.Iterator { return elements.makeIterator() } } σϑΥϧτͰ࣮૷͞ΕΔIndexingIterator಺෦Ͱɺؔ਺ݺͼग़͠ʹίε τ͕͔͔Δ৔߹͕͋ΔͨΊContiguousArray.IteratorΛެ։͢Δ @_versioned, @_inlineable͕privateଐੑͰͳ͘ͳΕ͹ΑΓద੾ʹ࠷దԽ͞Ε Δ࣮૷͕Ͱ͖Δ͕... extension Bag0: RandomAccessCollection { public var startIndex: Int { return buffer.startIndex } public var endIndex: Int { return buffer.endIndex } public func index(after i: Int) -> Int { return i + 1 } public subscript(position: Int) -> Element { return buffer[position].element } }

Slide 12

Slide 12 text

Optimization 4 Optimization with use case

Slide 13

Slide 13 text

@discardableResult public mutating func remove(for key: Key) -> Element? { if let index = elements.indices.reversed().first(where: { keyRawValues[$0] == key.value }) { keyRawValues.remove(at: index) return elements.remove(at: index) } return nil } ReactiveSwiftͰ͸ɺ͋ͱ͔Β௥Ճ͞Εͨ΋ͷͷํ͕remove(for:)͞Ε΍͍͢ ͱ͍͏ߟ͑Ͱٯॱʹ૞ࡧ͍ͯͨ͠ͷΛݱࡏͷ༻్ʹ߹Θͤͯ΍ΊΔ ͜ͷΑ͏ʹɺར༻ํ๏ʹ͚ͩ͢͜͠าΈدΔͷ΋༗ޮ @discardableResult public mutating func remove(for key: Key) -> Element? { if let index = elements.indices.first(where: { keyRawValues[$0] == key.value }) { keyRawValues.remove(at: index) return elements.remove(at: index) } return nil }

Slide 14

Slide 14 text

Measurement

Slide 15

Slide 15 text

Iteration 1,000,000 elements func testBag0() { typealias Bag = Bag0 let count = 1000_000 var bag = Bag() for _ in 0..

Slide 16

Slide 16 text

Iteration 1,000,000 elements 10x

Slide 17

Slide 17 text

Add, Iteration, Remove 10,000 elements func testBag0() { typealias Bag = Bag0 let count = 10_000 measure { var bag = Bag() var keys = [Bag.Key]() for _ in 0..

Slide 18

Slide 18 text

Add, Iteration, Remove 10,000 elements 200x

Slide 19

Slide 19 text

No content