Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Optimizing Swift Collection

Optimizing Swift Collection

potatotips 49

Twitter hashtag: #potatotips

Ryo Aoyama

March 13, 2018
Tweet

More Decks by Ryo Aoyama

Other Decks in Programming

Transcript

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

    View full-size slide

  2. Profile
    - Ryo Aoyama

    - CyberAgent/FRESH! 2016~

    - GitHub: @ra1028

    - Twitter: @ra1028fe5

    View full-size slide

  3. ReactiveSwift.Bag
    - ཁૉΛ௥Ճ͢Δ͜ͱ͕Ͱ͖Δ

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

    - Bag: RandomAccessCollection

    View full-size slide

  4. 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
    }
    }

    View full-size slide

  5. 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
    }
    }

    View full-size slide

  6. Optimization 1
    ContiguousArray

    View full-size slide

  7. Element͕Class͔@objcͷͱ͖ʹϝϞϦͷ࿈ଓͨ͠ྖҬʹ֨
    ೲ͠ɺΠςϨʔγϣϯΛߴ଎ԽͰ͖Δ

    ͜ͷಛੑΛ׆͔ͨ͢ΊɺElementͱKeyͰผʑͷ഑ྻΛ֬อ
    public struct Bag1 {
    private var elements = ContiguousArray()
    private var keys = ContiguousArray()
    private var buffer = [(key: Key, element: Element)]()

    View full-size slide

  8. Optimization 2
    Use optimized stdlib

    View full-size slide

  9. 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.==Λ࢖͏

    View full-size slide

  10. Optimization 3
    Custom Iterator

    View full-size slide

  11. 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
    }
    }

    View full-size slide

  12. Optimization 4
    Optimization with use case

    View full-size slide

  13. @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
    }

    View full-size slide

  14. Iteration 1,000,000 elements
    func testBag0() {
    typealias Bag = Bag0
    let count = 1000_000
    var bag = Bag()
    for _ in 0..let object = Object()
    bag.add(object)
    }
    measure {
    var trash = [Object]()
    trash.reserveCapacity(count)
    for object in bag {
    trash.append(object)
    }
    XCTAssertEqual(trash.count, count)
    }
    }

    View full-size slide

  15. Iteration 1,000,000 elements
    10x

    View full-size slide

  16. 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..let object = Object()
    let key = bag.add(object)
    keys.append(key)
    }
    var trash = [Object]()
    trash.reserveCapacity(count)
    for object in bag {
    trash.append(object)
    }
    for key in keys {
    bag.remove(for: key)
    }
    XCTAssertEqual(trash.count, count)
    XCTAssertEqual(bag.count, 0)
    }
    }

    View full-size slide

  17. Add, Iteration, Remove 10,000 elements
    200x

    View full-size slide