Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

The Mysterious Swift Performance

The Mysterious Swift Performance

The Swift Programming language runtime performance tricks & tips.

Marcin Krzyzanowski

April 22, 2017
Tweet

More Decks by Marcin Krzyzanowski

Other Decks in Programming

Transcript

  1. me • Marcin Krzyżanowski • blog.krzyzanowskim.com • Work at -

    the leading cross- platform solution for integrating PDFs into your app • twitter: @krzyzanowskim • github.com/krzyzanowskim • OSS: CryptoSwift, ObjectivePGP, Natalie….
  2. Retrospective • WWDC surprise • Objective-C without the baggage of

    C • The language is called Swift and it totally rules
  3. Retrospective • WWDC surprise • Objective-C without the baggage of

    C • The language is called Swift and it totally rules
  4. Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0

    Swift 3.1 "Python gets utterly crushed"
  5. On the path to the Fast code Swift is designed

    for the programming performance, not for the runtime performance (C. Lattner)
  6. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4)
  7. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4) Expression is too complex to be solved in reasonable time
  8. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4) • let arr:[Int] = [1] + [2] + [3] + [4] + [5] + [6] Expression is too complex to be solved in reasonable time
  9. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4) • let arr:[Int] = [1] + [2] + [3] + [4] + [5] + [6] • Script Expression is too complex to be solved in reasonable time
  10. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4) • let arr:[Int] = [1] + [2] + [3] + [4] + [5] + [6] • Script • Not optimized (-Onone) Expression is too complex to be solved in reasonable time
  11. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4) • let arr:[Int] = [1] + [2] + [3] + [4] + [5] + [6] • Script • Not optimized (-Onone) • Really slow Expression is too complex to be solved in reasonable time
  12. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4) • let arr:[Int] = [1] + [2] + [3] + [4] + [5] + [6] • Script • Not optimized (-Onone) • Really slow • Compiled Natalie is ~500% faster Expression is too complex to be solved in reasonable time
  13. Compilation • SWIFT_OPTIMIZATION_LEVEL • -Onone • debug build is ~30-times

    slower that release • -O • -Owholemodule • SWIFT_DISABLE_SAFETY_CHECKS • Yes/No • -Ounchecked swiftc -Owholemodule sample.swift
  14. Basic optimizations • to eliminate additional checks use: &+, &-

    • bitcast values if it doesn’t need a validation • UInt32(truncatingBitPattern: Int) add or substract
  15. Basic optimizations • modify in place • no copy on

    modify • pass-by-value-and-copy-back • The compiler may optimize an inout variable to pass-by- reference, rather than copying in and out. • core(block: &array) func core(block: inout Array<UInt8>) https://twitter.com/chriseidhof/status/815839860913270784
  16. Basic optimizations • Int, Int32, Int64 is optimized • UInt,

    UInt32, UInt64 is not • Improve > 2x by using Int non-obvious The innocent loop: var count: UInt64 = 0 for index in 0 ..< loopLimit { count += UInt64(localArray[Int(index)]) } takes 2.4 times as long if preceded by: let loopLimit = UInt64(nbrBytes) compared to: let loopLimit = Int(nbrBytes)
  17. Basic optimizations • for-loop over map • for-loop over reduce

    iterations extension Dictionary { /// An immutable version of update. Returns a new dictionary containing /// self’s values and the key/value passed in. func withUpdate(key: Key, value: Value) -> Dictionary<Key, Value> { var result = self result[key] = value return result } } let dict1 = ["joe": 3, "mary": 4] let dict2 = dict1.reduce([:]) { dict2, element in dict2.withUpdate(element.0, value: element.1 * 5) } var dict2 = [Key:Value](minCapacity: dict1.count) for (k,v) in dict1 { let (k1,v1) = transform(k,v) dict2[k1] = v1 }
  18. Less Swift in Swift • map is eager • lazy

    map is much better • lazy is default now • Array<UInt8> turns out to be nearly 10% slower compared to: UnsafeMutablePointer<UInt8>.allocate(capacity:) - var returnArray:[UInt8] = array.map({ _ in return 0 }) + var returnArray:[UInt8] = [UInt8](count: array.count, repeatedValue: 0)
  19. @inline(__always) • Automatic inlining (AI ) • Inline all the

    things with @inline(__always) • @inlinable is the future (@_inlineable) • transparent functions are implicitly marked @_inlineable • Force inlining with @_transparent • be careful with ABI
  20. Allocations • Stack is cheap • Heap is expensive •

    Pre-allocation is cheap • Swift never implicitly puts parts of large structs on the heap.
  21. 1 func arrayAlloc() { 2 var arr = [Int](repeating: 0,

    count: 1) 3 arr.append(-1) 4 arr.append(1) 5 } Allocations
  22. 1 func arrayAlloc() { 2 var arr = [Int](repeating: 0,

    count: 1) 3 arr.append(-1) 4 arr.append(1) 5 } Allocations (lldb) register read rsp rsp = 0x00007fff5fbff630 (lldb) frame variable -L 0x00007fff5fbff658: ([Int]) arr = 3 values { 0x00007fa5391de610: [0] = 0 0x00007fa53e6bf3f0: [1] = -1 0x00007fa53e6a91d0: [2] = 1 }
  23. 1 func arrayAlloc() { 2 var arr = [Int](repeating: 0,

    count: 1) 3 DispatchQueue(label: "queue").async { 4 arr.append(1) 5 } 6 arr.append(-1) 7 } Allocations
  24. 1 func arrayAlloc() { 2 var arr = [Int](repeating: 0,

    count: 1) 3 DispatchQueue(label: "queue").async { 4 arr.append(1) 5 } 6 arr.append(-1) 7 } Allocations (lldb) register read rsp rsp = 0x000070000ec06c00 (lldb) fr variable -L 0x000070000ec06c18: (@lvalue [Int]) arr = 0x0000000100d002b0: { 0x0000000100d002b0: &arr = 2 values { 0x00007fd7df41ae10: [0] = 0 0x00007fd7df485c90: [1] = -1 0x00007fd7df4829b0: [2] = 1 }
  25. Allocations sil hidden @SILInspector.test () -> () : $@convention(thin) ()

    -> () { bb0: %0 = alloc_box ${ var Array<Int> }, var, name "arr", loc "<stdin>":5:9, scope 2 // users: %59, %34, %33, %1 %1 = project_box %0 : ${ var Array<Int> }, 0, loc "<stdin>":5:9, scope 2 // users: %57, %12 %2 = metatype $@thin Array<Int>.Type, loc "<stdin>":5:15, scope 2 // user: %11 %3 = integer_literal $Builtin.Int64, 0, loc "<stdin>":5:37, scope 2 // user: %4 %4 = struct $Int (%3 : $Builtin.Int64), loc "<stdin>":5:37, scope 2 // user: %6 %5 = alloc_stack $Int, loc "<stdin>":5:37, scope 2 // users: %10, %6, %13 store %4 to %5 : $*Int, loc "<stdin>":5:37, scope 2 // id: %6
  26. Allocations sil-instruction ::= 'alloc_box' sil-type (',' debug-var-attr)* Allocates a reference-counted

    @box on the heap large enough to hold a value of type T, along with a retain count and any other metadata required by the runtime. The result of the instruction is the reference-counted @box reference that owns the box. The project_box instruction is used to retrieve the address of the value inside the box. 1 func arrayAlloc() { 2 var arr = [Int](repeating: 0, count: 1) 3 DispatchQueue(label: "queue").async { 4 arr.append(1) 5 } 6 arr.append(-1) 7 }
  27. 1 func arrayAlloc() { 2 var s = MyStruct() 3

    DispatchQueue(label: "queue").async { 4 s.perform() 5 } 6 7 } Allocations
  28. 1 func arrayAlloc() { 2 var s = MyStruct() 3

    DispatchQueue(label: "queue").async { 4 s.perform() 5 } 6 7 } Allocations (lldb) register read rsp rsp = 0x000070000ec06c00 (lldb) fr variable -L 0x00007fff5fbff680: (@lvalue MyStruct) s = 0x0000000100f06820: { 0x0000000100f06820: &s = { 0x0000000100f06820: value = "test" } }
  29. 1 public struct Heap1: Test { 2 3 public init()

    {} 4 5 private static var unicodeHexMap: [UnicodeScalar: UInt8] { 6 return ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 7 } 8 9 public func hexToBytes(hex: String) -> [UInt8] { 10 var bytes: [UInt8] = [] 11 bytes.reserveCapacity(hex.utf8.count / 2) 12 13 var buffer: UInt8? 14 for char in hex.unicodeScalars.lazy { 15 guard let value = Heap1.unicodeHexMap[char] else { return [] } 16 if let b = buffer { 17 let byte = b << 4 | value 18 bytes.append(byte) 19 buffer = nil 20 } else { 21 buffer = value 22 } 23 } 24 if let b = buffer { 25 bytes.append(b) 26 } 27 return bytes 28 } Allocations
  30. 1 public struct Heap1: Test { 2 3 public init()

    {} 4 5 private static var unicodeHexMap: [UnicodeScalar: UInt8] { 6 return ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 7 } 8 9 public func hexToBytes(hex: String) -> [UInt8] { 10 var bytes: [UInt8] = [] 11 bytes.reserveCapacity(hex.utf8.count / 2) 12 13 var buffer: UInt8? 14 for char in hex.unicodeScalars.lazy { 15 guard let value = Heap1.unicodeHexMap[char] else { return [] } 16 if let b = buffer { 17 let byte = b << 4 | value 18 bytes.append(byte) 19 buffer = nil 20 } else { 21 buffer = value 22 } 23 } 24 if let b = buffer { 25 bytes.append(b) 26 } 27 return bytes 28 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff418: (String) hex = "0102030405" 0x00007fff5fbff400: (Example.Stack2) self = { 0x00007fff5fbff400: description = "Stack static dictionary" } 0x00007fff5fbff498: ([UInt8]) bytes = 5 values { 0x00007fa5268260d0: [0] = 1 0x00007fa52683e9c0: [1] = 2 (…) } 0x00007fff5fbff490: (UInt8?) buffer = nil Allocations
  31. 1 public struct Heap1: Test { 2 3 public init()

    {} 4 5 private static var unicodeHexMap: [UnicodeScalar: UInt8] { 6 return ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 7 } 8 9 public func hexToBytes(hex: String) -> [UInt8] { 10 var bytes: [UInt8] = [] 11 bytes.reserveCapacity(hex.utf8.count / 2) 12 13 var buffer: UInt8? 14 for char in hex.unicodeScalars.lazy { 15 guard let value = Heap1.unicodeHexMap[char] else { return [] } 16 if let b = buffer { 17 let byte = b << 4 | value 18 bytes.append(byte) 19 buffer = nil 20 } else { 21 buffer = value 22 } 23 } 24 if let b = buffer { 25 bytes.append(b) 26 } 27 return bytes 28 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff418: (String) hex = "0102030405" 0x00007fff5fbff400: (Example.Stack2) self = { 0x00007fff5fbff400: description = "Stack static dictionary" } 0x00007fff5fbff498: ([UInt8]) bytes = 5 values { 0x00007fa5268260d0: [0] = 1 0x00007fa52683e9c0: [1] = 2 (…) } 0x00007fff5fbff490: (UInt8?) buffer = nil Allocations 10_000 iterations ~= 0.5495s
  32. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 let

    unicodeHexMap: [UnicodeScalar: UInt8] = ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 3 4 var bytes: [UInt8] = [] 5 bytes.reserveCapacity(hex.utf8.count / 2) 6 7 var buffer: UInt8? 8 for char in hex.unicodeScalars.lazy { 9 guard let value = unicodeHexMap[char] else { return [] } 10 if let byte = buffer { 11 bytes.append(byte << 4 | value) 12 buffer = nil 13 } else { 14 buffer = value 15 } 16 } 17 18 if let byte = buffer { 19 bytes.append(byte) 20 } 21 22 return bytes 23 } Allocations
  33. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 let

    unicodeHexMap: [UnicodeScalar: UInt8] = ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 3 4 var bytes: [UInt8] = [] 5 bytes.reserveCapacity(hex.utf8.count / 2) 6 7 var buffer: UInt8? 8 for char in hex.unicodeScalars.lazy { 9 guard let value = unicodeHexMap[char] else { return [] } 10 if let byte = buffer { 11 bytes.append(byte << 4 | value) 12 buffer = nil 13 } else { 14 buffer = value 15 } 16 } 17 18 if let byte = buffer { 19 bytes.append(byte) 20 } 21 22 return bytes 23 } (lldb) register read rsp rsp = 0x00007fff5fbff220 (lldb) frame variable -L 0x00007fff5fbff410: (String) hex = "0102030405" 0x00007fff5fbff3f8: (Example.Stack1) self = { 0x00007fff5fbff3f8: description = "local dictionary" } 0x00007fff5fbff490: ([UInt8]) bytes = 5 values { 0x00007fa531dc8420: [0] = 1 0x00007fa53e7381b0: [1] = 2 (…) } 0x00007fff5fbff488: (UInt8?) buffer = nil 0x00007fff5fbff298: ([UnicodeScalar : UInt8]) unicodeHexMap = 16 key/value pairs { 0x00007fa534a02010: [0] = { 0x00007fa534a02010: key = U'e' 0x00007fa534a02014: value = 14 } 0x00007fa53e2e9950: [1] = { 0x00007fa53e2e9950: key = U'1' 0x00007fa53e2e9954: value = 1 } (…) Allocations
  34. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 let

    unicodeHexMap: [UnicodeScalar: UInt8] = ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 3 4 var bytes: [UInt8] = [] 5 bytes.reserveCapacity(hex.utf8.count / 2) 6 7 var buffer: UInt8? 8 for char in hex.unicodeScalars.lazy { 9 guard let value = unicodeHexMap[char] else { return [] } 10 if let byte = buffer { 11 bytes.append(byte << 4 | value) 12 buffer = nil 13 } else { 14 buffer = value 15 } 16 } 17 18 if let byte = buffer { 19 bytes.append(byte) 20 } 21 22 return bytes 23 } 10_000 iterations ~= 0.0651s (lldb) register read rsp rsp = 0x00007fff5fbff220 (lldb) frame variable -L 0x00007fff5fbff410: (String) hex = "0102030405" 0x00007fff5fbff3f8: (Example.Stack1) self = { 0x00007fff5fbff3f8: description = "local dictionary" } 0x00007fff5fbff490: ([UInt8]) bytes = 5 values { 0x00007fa531dc8420: [0] = 1 0x00007fa53e7381b0: [1] = 2 (…) } 0x00007fff5fbff488: (UInt8?) buffer = nil 0x00007fff5fbff298: ([UnicodeScalar : UInt8]) unicodeHexMap = 16 key/value pairs { 0x00007fa534a02010: [0] = { 0x00007fa534a02010: key = U'e' 0x00007fa534a02014: value = 14 } 0x00007fa53e2e9950: [1] = { 0x00007fa53e2e9950: key = U'1' 0x00007fa53e2e9954: value = 1 } (…) Allocations
  35. 1 public struct Stack2: Test { 2 3 public init()

    {} 4 5 private static let unicodeHexMap: [UnicodeScalar: UInt8] = ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 6 7 public func hexToBytes(hex: String) -> [UInt8] { 8 var bytes: [UInt8] = [] 9 bytes.reserveCapacity(hex.utf8.count / 2) 10 11 var buffer: UInt8? 12 for char in hex.unicodeScalars.lazy { 13 guard let value = Stack2.unicodeHexMap[char] else { return [] } 14 if let byte = buffer { 15 bytes.append(byte << 4 | value) 16 buffer = nil 17 } else { 18 buffer = value 19 } 20 } 21 22 if let byte = buffer { 23 bytes.append(byte) 24 } 25 26 return bytes 27 } 28 } Allocations
  36. 1 public struct Stack2: Test { 2 3 public init()

    {} 4 5 private static let unicodeHexMap: [UnicodeScalar: UInt8] = ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 6 7 public func hexToBytes(hex: String) -> [UInt8] { 8 var bytes: [UInt8] = [] 9 bytes.reserveCapacity(hex.utf8.count / 2) 10 11 var buffer: UInt8? 12 for char in hex.unicodeScalars.lazy { 13 guard let value = Stack2.unicodeHexMap[char] else { return [] } 14 if let byte = buffer { 15 bytes.append(byte << 4 | value) 16 buffer = nil 17 } else { 18 buffer = value 19 } 20 } 21 22 if let byte = buffer { 23 bytes.append(byte) 24 } 25 26 return bytes 27 } 28 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff418: (String) hex = "0102030405" 0x00007fff5fbff400: (Example.Stack2) self = { 0x00007fff5fbff400: description = "Stack static dictionary" } 0x00007fff5fbff498: ([UInt8]) bytes = 5 values { 0x00007fa5268260d0: [0] = 1 0x00007fa52683e9c0: [1] = 2 (…) } 0x00007fff5fbff490: (UInt8?) buffer = nil 0x00000001000bdfe0: ([UnicodeScalar : UInt8]) unicodeHexMap = 16 key/value pairs { 0x00007fa534b6e390: [0] = { 0x00007fa534b6e390: key = U'e' 0x00007fa534b6e394: value = 14 } 0x00007fa534bccd10: [1] = { 0x00007fa534bccd10: key = U'1' 0x00007fa534bccd14: value = 1 } (…) Allocations
  37. 1 public struct Stack2: Test { 2 3 public init()

    {} 4 5 private static let unicodeHexMap: [UnicodeScalar: UInt8] = ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 6 7 public func hexToBytes(hex: String) -> [UInt8] { 8 var bytes: [UInt8] = [] 9 bytes.reserveCapacity(hex.utf8.count / 2) 10 11 var buffer: UInt8? 12 for char in hex.unicodeScalars.lazy { 13 guard let value = Stack2.unicodeHexMap[char] else { return [] } 14 if let byte = buffer { 15 bytes.append(byte << 4 | value) 16 buffer = nil 17 } else { 18 buffer = value 19 } 20 } 21 22 if let byte = buffer { 23 bytes.append(byte) 24 } 25 26 return bytes 27 } 28 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff418: (String) hex = "0102030405" 0x00007fff5fbff400: (Example.Stack2) self = { 0x00007fff5fbff400: description = "Stack static dictionary" } 0x00007fff5fbff498: ([UInt8]) bytes = 5 values { 0x00007fa5268260d0: [0] = 1 0x00007fa52683e9c0: [1] = 2 (…) } 0x00007fff5fbff490: (UInt8?) buffer = nil 0x00000001000bdfe0: ([UnicodeScalar : UInt8]) unicodeHexMap = 16 key/value pairs { 0x00007fa534b6e390: [0] = { 0x00007fa534b6e390: key = U'e' 0x00007fa534b6e394: value = 14 } 0x00007fa534bccd10: [1] = { 0x00007fa534bccd10: key = U'1' 0x00007fa534bccd14: value = 1 } (…) Allocations alloc_global @static SILInspector.Stack2_1.(unicodeHexMap2 in _A3C0505C99219871A6CE6164E5787308) : [Swift.UnicodeScalar : Swift.UInt8], … %1 = global_addr @static SILInspector.Stack2_1.(unicodeHexMap2 in _A3C0505C99219871A6CE6164E5787308) : [Swift.UnicodeScalar : Swift.UInt8] : $*Dictionary<UnicodeScalar, UInt8>, …
  38. 1 public struct Stack2: Test { 2 3 public init()

    {} 4 5 private static let unicodeHexMap: [UnicodeScalar: UInt8] = ["0": 0, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9, "a": 10, "b": 11, "c": 12, "d": 13, "e": 14, "f": 15] 6 7 public func hexToBytes(hex: String) -> [UInt8] { 8 var bytes: [UInt8] = [] 9 bytes.reserveCapacity(hex.utf8.count / 2) 10 11 var buffer: UInt8? 12 for char in hex.unicodeScalars.lazy { 13 guard let value = Stack2.unicodeHexMap[char] else { return [] } 14 if let byte = buffer { 15 bytes.append(byte << 4 | value) 16 buffer = nil 17 } else { 18 buffer = value 19 } 20 } 21 22 if let byte = buffer { 23 bytes.append(byte) 24 } 25 26 return bytes 27 } 28 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff418: (String) hex = "0102030405" 0x00007fff5fbff400: (Example.Stack2) self = { 0x00007fff5fbff400: description = "Stack static dictionary" } 0x00007fff5fbff498: ([UInt8]) bytes = 5 values { 0x00007fa5268260d0: [0] = 1 0x00007fa52683e9c0: [1] = 2 (…) } 0x00007fff5fbff490: (UInt8?) buffer = nil 0x00000001000bdfe0: ([UnicodeScalar : UInt8]) unicodeHexMap = 16 key/value pairs { 0x00007fa534b6e390: [0] = { 0x00007fa534b6e390: key = U'e' 0x00007fa534b6e394: value = 14 } 0x00007fa534bccd10: [1] = { 0x00007fa534bccd10: key = U'1' 0x00007fa534bccd14: value = 1 } (…) 10_000 iterations ~= 0.0116s Allocations alloc_global @static SILInspector.Stack2_1.(unicodeHexMap2 in _A3C0505C99219871A6CE6164E5787308) : [Swift.UnicodeScalar : Swift.UInt8], … %1 = global_addr @static SILInspector.Stack2_1.(unicodeHexMap2 in _A3C0505C99219871A6CE6164E5787308) : [Swift.UnicodeScalar : Swift.UInt8] : $*Dictionary<UnicodeScalar, UInt8>, …
  39. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 var

    hex = hex 3 var output = [UInt8]() 4 5 output.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) 6 var buffer: UInt8? 7 for char in hex.unicodeScalars.lazy { 8 guard char.value >= 48 && char.value <= 102 else { 9 output.removeAll() 10 return output 11 } 12 let v: UInt8 13 let c: UInt8 = UInt8(char.value) 14 switch c { 15 case 48 ... 57: 16 v = c - 48 17 case 65 ... 70: 18 v = c - 55 19 case 97 ... 102: 20 v = c - 87 21 default: 22 output.removeAll() 23 return output 24 } 25 if let b = buffer { 26 output.append(b << 4 | v) 27 buffer = nil 28 } else { 29 buffer = v 30 } 31 } 32 if let b = buffer { 33 output.append(b) 34 } 35 return output 36 } Ranges
  40. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 var

    hex = hex 3 var output = [UInt8]() 4 5 output.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) 6 var buffer: UInt8? 7 for char in hex.unicodeScalars.lazy { 8 guard char.value >= 48 && char.value <= 102 else { 9 output.removeAll() 10 return output 11 } 12 let v: UInt8 13 let c: UInt8 = UInt8(char.value) 14 switch c { 15 case 48 ... 57: 16 v = c - 48 17 case 65 ... 70: 18 v = c - 55 19 case 97 ... 102: 20 v = c - 87 21 default: 22 output.removeAll() 23 return output 24 } 25 if let b = buffer { 26 output.append(b << 4 | v) 27 buffer = nil 28 } else { 29 buffer = v 30 } 31 } 32 if let b = buffer { 33 output.append(b) 34 } 35 return output 36 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff398: (String) hex = "0102030405" 0x00007fff5fbff380: (Example.Range1) self = { 0x00007fff5fbff380: description = "ranged case" } 0x00007fff5fbff420: (String) hex = "0102030405" 0x00007fff5fbff418: ([UInt8]) output = 5 values { 0x00007f81306d9380: [0] = 1 0x00007f81306d8c00: [1] = 2 0x00007f81306d9980: [2] = 3 0x00007f81306da1c0: [3] = 4 0x00007f81306da470: [4] = 5 } 0x00007fff5fbff410: (UInt8?) buffer = nil Ranges
  41. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 var

    hex = hex 3 var output = [UInt8]() 4 5 output.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) 6 var buffer: UInt8? 7 for char in hex.unicodeScalars.lazy { 8 guard char.value >= 48 && char.value <= 102 else { 9 output.removeAll() 10 return output 11 } 12 let v: UInt8 13 let c: UInt8 = UInt8(char.value) 14 switch c { 15 case 48 ... 57: 16 v = c - 48 17 case 65 ... 70: 18 v = c - 55 19 case 97 ... 102: 20 v = c - 87 21 default: 22 output.removeAll() 23 return output 24 } 25 if let b = buffer { 26 output.append(b << 4 | v) 27 buffer = nil 28 } else { 29 buffer = v 30 } 31 } 32 if let b = buffer { 33 output.append(b) 34 } 35 return output 36 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff398: (String) hex = "0102030405" 0x00007fff5fbff380: (Example.Range1) self = { 0x00007fff5fbff380: description = "ranged case" } 0x00007fff5fbff420: (String) hex = "0102030405" 0x00007fff5fbff418: ([UInt8]) output = 5 values { 0x00007f81306d9380: [0] = 1 0x00007f81306d8c00: [1] = 2 0x00007f81306d9980: [2] = 3 0x00007f81306da1c0: [3] = 4 0x00007f81306da470: [4] = 5 } 0x00007fff5fbff410: (UInt8?) buffer = nil 10_000 iterations ~= 0.0214s Ranges
  42. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 var

    hex = hex 3 var output = [UInt8]() 4 5 output.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) 6 var buffer: UInt8? 7 for char in hex.unicodeScalars.lazy { 8 guard char.value >= 48 && char.value <= 102 else { 9 output.removeAll() 10 return output 11 } 12 let v: UInt8 13 let c: UInt8 = UInt8(char.value) 14 switch c { 15 case let c where c <= 57: 16 v = c - 48 17 case let c where c >= 65 && c <= 70: 18 v = c - 55 19 case let c where c >= 97: 20 v = c - 87 21 default: 22 output.removeAll() 23 return output 24 } 25 if let b = buffer { 26 output.append(b << 4 | v) 27 buffer = nil 28 } else { 29 buffer = v 30 } 31 } 32 if let b = buffer { 33 output.append(b) 34 } 35 return output 36 } Ranges
  43. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 var

    hex = hex 3 var output = [UInt8]() 4 5 output.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) 6 var buffer: UInt8? 7 for char in hex.unicodeScalars.lazy { 8 guard char.value >= 48 && char.value <= 102 else { 9 output.removeAll() 10 return output 11 } 12 let v: UInt8 13 let c: UInt8 = UInt8(char.value) 14 switch c { 15 case let c where c <= 57: 16 v = c - 48 17 case let c where c >= 65 && c <= 70: 18 v = c - 55 19 case let c where c >= 97: 20 v = c - 87 21 default: 22 output.removeAll() 23 return output 24 } 25 if let b = buffer { 26 output.append(b << 4 | v) 27 buffer = nil 28 } else { 29 buffer = v 30 } 31 } 32 if let b = buffer { 33 output.append(b) 34 } 35 return output 36 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff398: (String) hex = "0102030405" 0x00007fff5fbff380: (Example.Range1) self = { 0x00007fff5fbff380: description = "ranged case" } 0x00007fff5fbff420: (String) hex = "0102030405" 0x00007fff5fbff418: ([UInt8]) output = 5 values { 0x00007f81306d9380: [0] = 1 0x00007f81306d8c00: [1] = 2 0x00007f81306d9980: [2] = 3 0x00007f81306da1c0: [3] = 4 0x00007f81306da470: [4] = 5 } 0x00007fff5fbff410: (UInt8?) buffer = nil Ranges
  44. 1 public func hexToBytes(hex: String) -> [UInt8] { 2 var

    hex = hex 3 var output = [UInt8]() 4 5 output.reserveCapacity(hex.unicodeScalars.lazy.underestimatedCount) 6 var buffer: UInt8? 7 for char in hex.unicodeScalars.lazy { 8 guard char.value >= 48 && char.value <= 102 else { 9 output.removeAll() 10 return output 11 } 12 let v: UInt8 13 let c: UInt8 = UInt8(char.value) 14 switch c { 15 case let c where c <= 57: 16 v = c - 48 17 case let c where c >= 65 && c <= 70: 18 v = c - 55 19 case let c where c >= 97: 20 v = c - 87 21 default: 22 output.removeAll() 23 return output 24 } 25 if let b = buffer { 26 output.append(b << 4 | v) 27 buffer = nil 28 } else { 29 buffer = v 30 } 31 } 32 if let b = buffer { 33 output.append(b) 34 } 35 return output 36 } (lldb) register read rsp rsp = 0x00007fff5fbff230 (lldb) frame variable -L 0x00007fff5fbff398: (String) hex = "0102030405" 0x00007fff5fbff380: (Example.Range1) self = { 0x00007fff5fbff380: description = "ranged case" } 0x00007fff5fbff420: (String) hex = "0102030405" 0x00007fff5fbff418: ([UInt8]) output = 5 values { 0x00007f81306d9380: [0] = 1 0x00007f81306d8c00: [1] = 2 0x00007f81306d9980: [2] = 3 0x00007f81306da1c0: [3] = 4 0x00007f81306da470: [4] = 5 } 0x00007fff5fbff410: (UInt8?) buffer = nil 10_000 iterations ~= 0.0072s Ranges
  45. 10_000 iterations 0s 0,15s 0,3s 0,45s 0,6s Closure Local Dictionary

    Global Dictionary Range No Range 0,007s 0,021s 0,012s 0,065s 0,550s 0,003s 0,004s 0,004s 0,006s 0,026s Release build Debug build
  46. 10_000 iterations 0s 0,02s 0,04s 0,06s 0,08s Local Dictionary Global

    Dictionary Range No Range 0,007s 0,021s 0,012s 0,065s 0,003s 0,004s 0,004s 0,006s Release build Debug build
  47. Array vs. ArraySlice • Array (value) • data structure •

    contains a group of elements • all of the same data type • each element identified by index • position of each element can be computed from its index • Array is convenient to use • Array is optimized (COW)
  48. Array vs. ArraySlice • ArraySlice is not an Array •

    Array is not an ArraySlice • ArraySlice is a slice of array • a view of the Array values • defined by range • ArraySlice = Array[startIndex…endIndex]
  49. Array vs. ArraySlice The ArraySlice type makes it fast and

    efficient for you to perform operations on sections of a larger array. Instead of copying over the elements of a slice to new storage, an ArraySlice instance presents a view onto the storage of a larger array. And because ArraySlice presents the same interface as Array, you can generally perform the same operations on a slice as you could on the original array.
  50. Array vs. ArraySlice • Generic types Array<T>, ArraySlice<T> • Not

    interchangeable 1 func use(array: Array<UInt8>) { 2 /* ... */ 3 } 4 5 func use(array: ArraySlice<UInt8>) { 6 /* ... */ 7 }
  51. Array vs. ArraySlice • Generic types Array<T>, ArraySlice<T> • Not

    interchangeable 1 func use(array: Array<UInt8>) { 2 /* ... */ 3 } 4 5 func use(array: ArraySlice<UInt8>) { 6 /* ... */ 7 } 8 9 func use<T>(array: T) where T == Array { 10 /* ... */ 11 } RandomReplaceableCollection
  52. Array vs. ArraySlice • Generic types Array<T>, ArraySlice<T> • Not

    interchangeable 1 func use(array: Array<UInt8>) { 2 /* ... */ 3 } 4 5 func use(array: ArraySlice<UInt8>) { 6 /* ... */ 7 } 8 9 func use<T>(array: T) where T == Array { 10 /* ... */ 11 } RandomReplaceableCollection func decrypt<C: Collection>(_ bytes: C) throws -> Array<UInt8> where C.Iterator.Element == UInt8, C.IndexDistance == Int, C.Index == Int, C.SubSequence: Collection, C.SubSequence.Iterator.Element == C.Iterator.Element
  53. Array vs. ArraySlice • Generic types Array<T>, ArraySlice<T> • Not

    interchangeable 1 func use(array: Array<UInt8>) { 2 /* ... */ 3 } 4 5 func use(array: ArraySlice<UInt8>) { 6 /* ... */ 7 } 8 9 func use<T>(array: T) where T == Array { 10 /* ... */ 11 } RandomReplaceableCollection func decrypt<C: Collection>(_ bytes: C) throws -> Array<UInt8> where C.Iterator.Element == UInt8, C.IndexDistance == Int, C.Index == Int, C.SubSequence: Collection, C.SubSequence.Iterator.Element == C.Iterator.Element Array(collection)
  54. Array vs. ArraySlice • Generic types Array<T>, ArraySlice<T> • Not

    interchangeable 1 func use(array: Array<UInt8>) { 2 /* ... */ 3 } 4 5 func use(array: ArraySlice<UInt8>) { 6 /* ... */ 7 } 8 9 func use<T>(array: T) where T == Array { 10 /* ... */ 11 } RandomReplaceableCollection func decrypt<C: Collection>(_ bytes: C) throws -> Array<UInt8> where C.Iterator.Element == UInt8, C.IndexDistance == Int, C.Index == Int, C.SubSequence: Collection, C.SubSequence.Iterator.Element == C.Iterator.Element Array(collection) COPY IS BAD
  55. Array vs. ArraySlice 1 func use(array: Array<UInt8>) { 2 use(array:

    array[array.indices]) 3 } 4 5 func use(array: ArraySlice<UInt8>) { 6 /* ... */ 7 } ✔
  56. Generics Generic code enables you to write flexible, reusable functions

    and types that can work with any type, subject to requirements that you define.
  57. Generics 1 public protocol Pingable { func ping() -> Self

    } 2 public protocol Playable { func play() } 3 4 extension Int : Pingable { 5 public func ping() -> Int { return self + 1 } 6 } 7 8 public class Game<T : Pingable> : Playable { 9 var t: T 10 11 public init (_ v: T) { t = v } 12 13 public func play() { 14 for _ in 0 ... 100_000_000 { t = t.ping() } 15 } 16 } 17 18 Game(10).play() https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst 0.1996s
  58. Generics 1 public protocol Pingable { func ping() -> Self

    } 2 public protocol Playable { func play() } 3 4 extension Int : Pingable { 5 public func ping() -> Int { return self + 1 } 6 } 7 8 public class Game<T : Pingable> : Playable { 9 var t: T 10 11 public init (_ v: T) { t = v } 12 13 @_specialize(Int) public func play() { 14 for _ in 0 ... 100_000_000 { t = t.ping() } 15 } 16 } 17 18 Game(10).play() https://github.com/apple/swift/blob/master/docs/OptimizationTips.rst 0.1996s 0.0564s
  59. Generics in SIL - public func play() sil @main.Game.play ()

    -> () : $@convention(method) <T where T : Pingable> (@guaranteed Game<T>) -> () { ... } - @_specialize(Int) public func play() sil [_specialize <Int>] @main.Game.play () -> () : $@convention(method) <T where T : Pingable> (@guaranteed Game<T>) -> () { ... } swiftc sample.swift -emit-silgen
  60. Generics in SIL left bb0 => right bb1 swiftc sample.swift

    -emit-sil Canonical SIL after “Generic specialization” optimization
  61. Generics & Optimization • Automatic specialization • in the same

    module • otherwise use @_specialization • Whole Module Optimization won’t help • Sometimes it won’t optimize for the same module • Avoid generics in public API
  62. thank you • Marcin Krzyżanowski • blog.krzyzanowskim.com • Work at

    - the leading cross- platform solution for integrating PDFs into your app • twitter: @krzyzanowskim • github.com/krzyzanowskim • OSS: CryptoSwift, ObjectivePGP, Natalie…. questions?