Stacks Image 11

Optimization Tips

This article is about improving your Swift application performance. It will be completed frequently.

Declare functions as final in classes

By declaring a function as final, you tell to the compiler it can't be overriden in a subclass. It will know that it can directly call this function implementation (direct dispatch) instead of having first to check if it does not need to call the implementation from a sub-class (dynamic dispatch).

Declaring a class as final will produce the same result.

Inline functions

Those familiar with C language should already know what an inline function is.

When you declare a function/method as inline you tel the compiler to always replace call to this function or method by the content of this function or method and hence prevent an overhead. In normal circonstances the compiler will determine if it inline methods or perform calls, it will also depends on the asked optimization level.

Let's say we have a function that processes an Int and we also wan't one doing the same processing but on an array of Int.

  1. We would usualy write something like:
func process(value: Int) {
    // processing code
}

func process(values: [Int]) {
    for value in values {
        process(value: value)
    }
}

In this first version we might "pay" the overhead of calling the process(value:) function multiple times inside the process(values:) function.

  1. A variant might be something like:
func process(value: Int) {
    process(values: [value])
}

func process(values: Int) {
    for value in values {
        // processing code
    }
}

In this second variant the overhead is reduced while working on an array but increased for single Int.

  1. A no overhead version, without using inline:
func process(value: Int) {
    // processing code
}

func process(values: Int) {
    for value in values {
        // processing code
    }
}

In this third variant, no overhead, but you have writen the code twice, not so good for code quality (you shouldn't have duplicated blocks in a source code).

  1. The no overhead version using inline
@inline(__always) private func actualProcess(value: Int) {
    // processing code
}

func process(value: Int) {
    actualProcess(value: Int)
}

func process(values: Int) {
    for value in values {
        actualProcess(value: Int)
    }
}

This fourth variant will produce a binary similar to the third one, but without having duplicated blocks.

Collections memory allocation

By collections we mean an Array, a Set or a Dictionary in Swift

Reserve capacity

Reserving beforehand the capacity will prevent to "pay" multiple capacity increase while adding elements to the collection

In a Set or a Dictionary it can be done at init time or after it

    var mySet = Set<Int>(minimumCapacity: 100)
    var myDic = [String:String](minimumCapacity: 100)
    
    // or
    
    var mySet = Set<Int>()
    mySet.reserveCapacity(100)
    
    var myDic = [String:String]()
    myDic.reserveCapacity(100)

For an Array you can do it only after initialization.

    var myArray = [Int]()
    myArray.reserveCapacity(100)

Remove all elements instead of creating new collections

The three collection types have a method removeAll(keepingCapacity: Bool) to fastly clean it.

Using this method instead of allocating a new object will save you the cost of deallocating previous instance and allocating new one.

    var myArray = [Int]()  
    // do something with array
    
    myArray.removeAll(keepingCapacity: true)
    // myArray is as new with an already reserved capacity

is faster than:

    var myArray = [Int]()
    // do something with array

    myArray = [Int]()
    myArray.reserveCapacity(…)