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

The Mysterious Swift Performance

The Mysterious Swift Performance

The Swift Programming language runtime performance tricks & tips.

Marcin Krzyzanowski

April 22, 2017
Tweet

More Decks by Marcin Krzyzanowski

Other Decks in Programming

Transcript

  1. The Mysterious Swift
    Performance
    2017

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. View Slide

  10. 1.4x

    View Slide

  11. Swift 2.x

    View Slide

  12. Swift 2.x

    View Slide

  13. Swift 2.x

    View Slide

  14. Swift 4
    124x
    all the things

    View Slide

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

    View Slide

  16. Compiled vs Script
    • Compiled

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  26. Compilation
    Swift Intermediate Language
    better Swift

    View Slide

  27. Basic optimizations

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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)

    View Slide

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

    View Slide

  35. 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)

    View Slide

  36. Inline

    View Slide

  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

    View Slide

  38. Allocation

    View Slide

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

    View Slide

  40. Allocations
    Pre-allocate
    array.reserveCapacity(1024)

    View Slide

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

    View Slide

  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
    }

    View Slide

  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

    View Slide

  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
    }

    View Slide

  45. Allocations

    View Slide

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

    View Slide

  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 }

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  68. And Now For Something
    Completely Different
    Array
    vs
    ArraySlice

    View Slide

  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)

    View Slide

  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]

    View Slide

  71. Array vs. ArraySlice

    View Slide

  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.

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  76. 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)

    View Slide

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

    View Slide

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

    View Slide

  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.

    View Slide

  80. View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  85. Generics in SIL

    View Slide

  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

    View Slide

  87. Let's Recap…

    View Slide

  88. Let's Recap…
    Debug
    is
    slow

    View Slide

  89. Let's Recap…
    Pre-allocate
    memory

    View Slide

  90. Let's Recap…
    Stack
    vs
    Heap

    View Slide

  91. Let's Recap…Copy
    is
    bad

    View Slide

  92. Let's Recap…
    Use carefully

    View Slide

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

    View Slide

  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?

    View Slide