Slide 1

Slide 1 text

The Mysterious Swift Performance 2017

Slide 2

Slide 2 text

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….

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0 Swift 3.1

Slide 6

Slide 6 text

Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0 Swift 3.1

Slide 7

Slide 7 text

Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0 Swift 3.1

Slide 8

Slide 8 text

Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0 Swift 3.1 "Python gets utterly crushed"

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

1.4x

Slide 11

Slide 11 text

Swift 2.x

Slide 12

Slide 12 text

Swift 2.x

Slide 13

Slide 13 text

Swift 2.x

Slide 14

Slide 14 text

Swift 4 124x all the things

Slide 15

Slide 15 text

On the path to the Fast code Swift is designed for the programming performance, not for the runtime performance (C. Lattner)

Slide 16

Slide 16 text

Compiled vs Script • Compiled

Slide 17

Slide 17 text

Compiled vs Script • Compiled • Compile times :-(

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Compilation Swift Intermediate Language better Swift

Slide 27

Slide 27 text

Basic optimizations

Slide 28

Slide 28 text

Basic optimizations • to eliminate additional checks use: &+, &- • bitcast values if it doesn’t need a validation • UInt32(truncatingBitPattern: Int) add or substract

Slide 29

Slide 29 text

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) https://twitter.com/chriseidhof/status/815839860913270784

Slide 30

Slide 30 text

Basic optimizations • struct over class • final class • private class obvious

Slide 31

Slide 31 text

Basic optimizations • Int, Int32, Int64 is optimized non-obvious

Slide 32

Slide 32 text

Basic optimizations • Int, Int32, Int64 is optimized • UInt, UInt32, UInt64 is not non-obvious

Slide 33

Slide 33 text

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)

Slide 34

Slide 34 text

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 { 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 }

Slide 35

Slide 35 text

Less Swift in Swift • map is eager • lazy map is much better • lazy is default now • Array turns out to be nearly 10% slower compared to: UnsafeMutablePointer.allocate(capacity:) - var returnArray:[UInt8] = array.map({ _ in return 0 }) + var returnArray:[UInt8] = [UInt8](count: array.count, repeatedValue: 0)

Slide 36

Slide 36 text

Inline

Slide 37

Slide 37 text

@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

Slide 38

Slide 38 text

Allocation

Slide 39

Slide 39 text

Allocations • Stack is cheap • Heap is expensive • Pre-allocation is cheap • Swift never implicitly puts parts of large structs on the heap.

Slide 40

Slide 40 text

Allocations Pre-allocate array.reserveCapacity(1024)

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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 }

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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 }

Slide 45

Slide 45 text

Allocations

Slide 46

Slide 46 text

Allocations sil hidden @SILInspector.test () -> () : $@convention(thin) () -> () { bb0: %0 = alloc_box ${ var Array }, var, name "arr", loc "":5:9, scope 2 // users: %59, %34, %33, %1 %1 = project_box %0 : ${ var Array }, 0, loc "":5:9, scope 2 // users: %57, %12 %2 = metatype $@thin Array.Type, loc "":5:15, scope 2 // user: %11 %3 = integer_literal $Builtin.Int64, 0, loc "":5:37, scope 2 // user: %4 %4 = struct $Int (%3 : $Builtin.Int64), loc "":5:37, scope 2 // user: %6 %5 = alloc_stack $Int, loc "":5:37, scope 2 // users: %10, %6, %13 store %4 to %5 : $*Int, loc "":5:37, scope 2 // id: %6

Slide 47

Slide 47 text

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 }

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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" } }

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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, …

Slide 59

Slide 59 text

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, …

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

And Now For Something Completely Different Array vs ArraySlice

Slide 69

Slide 69 text

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)

Slide 70

Slide 70 text

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]

Slide 71

Slide 71 text

Array vs. ArraySlice

Slide 72

Slide 72 text

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.

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

Array vs. ArraySlice • Generic types Array, ArraySlice • Not interchangeable 1 func use(array: Array) { 2 /* ... */ 3 } 4 5 func use(array: ArraySlice) { 6 /* ... */ 7 } 8 9 func use(array: T) where T == Array { 10 /* ... */ 11 } RandomReplaceableCollection func decrypt(_ bytes: C) throws -> Array 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

Slide 78

Slide 78 text

Array vs. ArraySlice 1 func use(array: Array) { 2 use(array: array[array.indices]) 3 } 4 5 func use(array: ArraySlice) { 6 /* ... */ 7 } ✔

Slide 79

Slide 79 text

Generics Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define.

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

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 : 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

Slide 82

Slide 82 text

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 : 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

Slide 83

Slide 83 text

Generics in SIL - public func play() sil @main.Game.play () -> () : $@convention(method) (@guaranteed Game) -> () { ... } - @_specialize(Int) public func play() sil [_specialize ] @main.Game.play () -> () : $@convention(method) (@guaranteed Game) -> () { ... } swiftc sample.swift -emit-silgen

Slide 84

Slide 84 text

Generics in SIL left bb0 => right bb1 swiftc sample.swift -emit-sil Canonical SIL after “Generic specialization” optimization

Slide 85

Slide 85 text

Generics in SIL

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

Let's Recap…

Slide 88

Slide 88 text

Let's Recap… Debug is slow

Slide 89

Slide 89 text

Let's Recap… Pre-allocate memory

Slide 90

Slide 90 text

Let's Recap… Stack vs Heap

Slide 91

Slide 91 text

Let's Recap…Copy is bad

Slide 92

Slide 92 text

Let's Recap… Use carefully

Slide 93

Slide 93 text

Let's Recap… Less Swift is faster Swift

Slide 94

Slide 94 text

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?