Share
Embed
The Mysterious Swift Performance
The Swift Programming language runtime performance tricks & tips.
Marcin Krzyzanowski
April 22, 2017
Transcript
94 slides
-
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…. -
Retrospective • WWDC surprise • Objective-C without the baggage of
C • The language is called Swift and it totally rules -
Retrospective • WWDC surprise • Objective-C without the baggage of
C • The language is called Swift and it totally rules -
Swift 1 Swift 1.2 Swift 2 Swift 2.3 Swift 3.0
Swift 3.1 "Python gets utterly crushed" -
On the path to the Fast code Swift is designed
for the programming performance, not for the runtime performance (C. Lattner) -
Compiled vs Script • Compiled • Compile times :-( •
let arr = String(1) + String(2) + String(3) + String(4) -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
Basic optimizations • to eliminate additional checks use: &+, &-
• bitcast values if it doesn’t need a validation • UInt32(truncatingBitPattern: Int) add or substract -
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 -
Basic optimizations • struct over class • final class •
private class obvious -
Basic optimizations • Int, Int32, Int64 is optimized • UInt,
UInt32, UInt64 is not non-obvious -
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) -
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 } -
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) -
@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 -
Allocations • Stack is cheap • Heap is expensive •
Pre-allocation is cheap • Swift never implicitly puts parts of large structs on the heap. -
1 func arrayAlloc() { 2 var arr = [Int](repeating: 0,
count: 1) 3 arr.append(-1) 4 arr.append(1) 5 } Allocations -
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 } -
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 -
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 } -
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 -
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 } -
1 func arrayAlloc() { 2 var s = MyStruct() 3
DispatchQueue(label: "queue").async { 4 s.perform() 5 } 6 7 } Allocations -
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" } } -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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>, … -
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>, … -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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) -
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] -
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. -
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 } -
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 -
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 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) -
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 -
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 } ✔ -
Generics Generic code enables you to write flexible, reusable functions
and types that can work with any type, subject to requirements that you define. -
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 -
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 -
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 -
Generics in SIL left bb0 => right bb1 swiftc sample.swift
-emit-sil Canonical SIL after “Generic specialization” optimization -
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 -
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?