The Mysterious Swift Performance

The Mysterious Swift Performance

The Swift Programming language runtime performance tricks & tips.

7b2fdba8077c8495b3caa6f36d0928da?s=128

Marcin Krzyzanowski

April 22, 2017
Tweet

Transcript

  1. The Mysterious Swift Performance 2017

  2. 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….
  3. Retrospective • WWDC surprise • Objective-C without the baggage of

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

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

    Swift 3.1
  6. Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0

    Swift 3.1
  7. Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0

    Swift 3.1
  8. Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0

    Swift 3.1 "Python gets utterly crushed"
  9. None
  10. 1.4x

  11. Swift 2.x

  12. Swift 2.x

  13. Swift 2.x

  14. Swift 4 124x all the things

  15. On the path to the Fast code Swift is designed

    for the programming performance, not for the runtime performance (C. Lattner)
  16. Compiled vs Script • Compiled

  17. Compiled vs Script • Compiled • Compile times :-(

  18. Compiled vs Script • Compiled • Compile times :-( •

    let arr = String(1) + String(2) + String(3) + String(4)
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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
  24. 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
  25. 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
  26. Compilation Swift Intermediate Language better Swift

  27. Basic optimizations

  28. Basic optimizations • to eliminate additional checks use: &+, &-

    • bitcast values if it doesn’t need a validation • UInt32(truncatingBitPattern: Int) add or substract
  29. 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
  30. Basic optimizations • struct over class • final class •

    private class obvious
  31. Basic optimizations • Int, Int32, Int64 is optimized non-obvious

  32. Basic optimizations • Int, Int32, Int64 is optimized • UInt,

    UInt32, UInt64 is not non-obvious
  33. 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)
  34. 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 }
  35. 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)
  36. Inline

  37. @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
  38. Allocation

  39. Allocations • Stack is cheap • Heap is expensive •

    Pre-allocation is cheap • Swift never implicitly puts parts of large structs on the heap.
  40. Allocations Pre-allocate array.reserveCapacity(1024)

  41. 1 func arrayAlloc() { 2 var arr = [Int](repeating: 0,

    count: 1) 3 arr.append(-1) 4 arr.append(1) 5 } Allocations
  42. 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 }
  43. 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
  44. 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 }
  45. Allocations

  46. 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
  47. 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 }
  48. 1 func arrayAlloc() { 2 var s = MyStruct() 3

    DispatchQueue(label: "queue").async { 4 s.perform() 5 } 6 7 } Allocations
  49. 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" } }
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. 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
  56. 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
  57. 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
  58. 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>, …
  59. 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>, …
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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
  68. And Now For Something Completely Different Array vs ArraySlice

  69. 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)
  70. 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]
  71. Array vs. ArraySlice

  72. 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.
  73. 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 }
  74. 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
  75. 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
  76. 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)
  77. 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
  78. 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 } ✔
  79. Generics Generic code enables you to write flexible, reusable functions

    and types that can work with any type, subject to requirements that you define.
  80. None
  81. 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
  82. 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
  83. 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
  84. Generics in SIL left bb0 => right bb1 swiftc sample.swift

    -emit-sil Canonical SIL after “Generic specialization” optimization
  85. Generics in SIL

  86. 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
  87. Let's Recap…

  88. Let's Recap… Debug is slow

  89. Let's Recap… Pre-allocate memory

  90. Let's Recap… Stack vs Heap

  91. Let's Recap…Copy is bad

  92. Let's Recap… Use carefully

  93. Let's Recap… Less Swift is faster Swift

  94. 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?