Writing High-Performance Swift Code- Part1

Shobhit Gupta
6 min readOct 27, 2020

whole module optimisation :-

By default Swift compiles each file individually. This allows Xcode to compile multiple files in parallel very quickly. However, compiling each file separately prevents certain compiler optimizations. Swift can also compile the entire program as if it were one file and optimize the program as if it were a single compilation unit. This mode is enabled using the swiftc command line flag -whole-module-optimization. Programs that are compiled in this mode will most likely take longer to compile, but may run faster.

This mode can be enabled using the Xcode build setting ‘Whole Module Optimization’.

Reducing Dynamic Dispatch:-

swift is very versatile language in nature. it gives you the power to use direct dispatch, dynamic dispatch and static dispatch in one place.

Swift gives the programmer the ability to improve runtime performance when necessary by removing or reducing dynamism. This section goes through several examples of language constructs that can be used to perform such an operation.

Dynamic Dispatch :-

Classes use dynamic dispatch for methods and property accesses by default. Thus in the following code snippet, a.aProperty, a.doSomething() and a.doSomethingElse() will all be invoked via dynamic dispatch:

class A {
var aProperty: [Int]
func doSomething() { ... }
dynamic doSomethingElse() { ... }
}

class B: A {
override var aProperty {
get { ... }
set { ... }
}

override func doSomething() { ... }
}

func usingAnA(_ a: A) {
a.doSomething()
a.aProperty = ...
}

In Swift, dynamic dispatch defaults to indirect invocation through a vtable [1]. If one attaches the dynamic keyword to the declaration, Swift will emit calls via Objective-C message send instead. In both cases this is slower than a direct function call because it prevents many compiler optimizations [2] in addition to the overhead of performing the indirect call itself. In performance critical code, one often will want to restrict this dynamic behavior.

Advice: Use ‘final’ when you know the declaration does not need to be overridden

The final keyword is a restriction on a declaration of a class, a method, or a property such that the declaration cannot be overridden. This implies that the compiler can emit direct function calls instead of indirect calls. For instance in the following C.array1 and D.array1will be accessed directly [3]. In contrast, D.array2 will be called via a vtable:

final class C {
// No declarations in class 'C' can be overridden.
var array1: [Int]
func doSomething() { ... }
}

class D {
final var array1: [Int] // 'array1' cannot be overridden by a computed property.
var array2: [Int] // 'array2' *can* be overridden by a computed property.
}

func usingC(_ c: C) {
c.array1[i] = ... // Can directly access C.array without going through dynamic dispatch.
c.doSomething() = ... // Can directly call C.doSomething without going through virtual dispatch.
}

func usingD(_ d: D) {
d.array1[i] = ... // Can directly access D.array1 without going through dynamic dispatch.
d.array2[i] = ... // Will access D.array2 through dynamic dispatch.
}

Advice: Use ‘private’ and ‘fileprivate’ when declaration does not need to be accessed outside of file

Applying the private or fileprivate keywords to a declaration restricts the visibility of the declaration to the file in which it is declared. This allows the compiler to be able to ascertain all other potentially overriding declarations. Thus the absence of any such declarations enables the compiler to infer the final keyword automatically and remove indirect calls for methods and field accesses accordingly. For instance in the following, e.doSomething() and f.myPrivateVar, will be able to be accessed directly assuming E, F do not have any overriding declarations in the same file:

private class E {
func doSomething() { ... }
}

class F {
fileprivate var myPrivateVar: Int
}

func usingE(_ e: E) {
e.doSomething() // There is no sub class in the file that declares this class.
// The compiler can remove virtual calls to doSomething()
// and directly call E's doSomething method.
}

func usingF(_ f: F) -> Int {
return f.myPrivateVar
}

advice: if ‘wmo’ is enabled use internal when a declaration does not need to be accessed outside module.

WMO (see section above) causes the compiler to compile a module’s sources all together at once. This allows the optimizer to have module wide visibility when compiling individual declarations. Since an internal declaration is not visible outside of the current module, the optimizer can then infer final by automatically discovering all potentially overridding declarations.

NOTE: Since in Swift the default access control level is internal anyways, by enabling Whole Module Optimization, one can gain additional devirtualization without any further work.

using continer type effectively:-

An important feature provided by the Swift standard library are the generic containers Array and Dictionary. This section will explain how to use these types in a performant manner.

advice : use value types in array

In Swift, types can be divided into two different categories: value types (structs, enums, tuples) and reference types (classes). A key distinction is that value types cannot be included inside an NSArray. Thus when using value types, the optimizer can remove most of the overhead in Array that is necessary to handle the possibility of the array being backed an NSArray.

Additionally, in contrast to reference types, value types only need reference counting if they contain, recursively, a reference type. By using value types without reference types, one can avoid additional retain, release traffic inside Array.

// Don't use a class here.
struct PhonebookEntry {
var name: String
var number: [Int]
}

var a: [PhonebookEntry]

Keep in mind that there is a trade-off between using large value types and using reference types. In certain cases, the overhead of copying and moving around large value types will outweigh the cost of removing the bridging and retain/release overhead.

advice : Use ContiguousArray with reference types when NSArray bridging is unnecessary

If you need an array of reference types and the array does not need to be bridged to NSArray, use ContiguousArray instead of Array:

class C { ... }
var a: ContiguousArray<C> = [C(...), C(...), ..., C(...)]

Advice: Use inplace mutation instead of object-reassignment

All standard library containers in Swift are value types that use COW (copy-on-write) [4] to perform copies instead of explicit copies. In many cases this allows the compiler to elide unnecessary copies by retaining the container instead of performing a deep copy. This is done by only copying the underlying container if the reference count of the container is greater than 1 and the container is mutated. For instance in the following, no copying will occur when d is assigned to c, but when d undergoes structural mutation by appending 2, d will be copied and then 2 will be appended to d:

var c: [Int] = [ ... ]
var d = c // No copy will occur here.
d.append(2) // A copy *does* occur here.

Sometimes COW can introduce additional unexpected copies if the user is not careful. An example of this is attempting to perform mutation via object-reassignment in functions. In Swift, all parameters are passed in at +1, i.e. the parameters are retained before a callsite, and then are released at the end of the callee. This means that if one writes a function like the following:

func append_one(_ a: [Int]) -> [Int] {
a.append(1)
return a
}
var a = [1, 2, 3]
a = append_one(a)

a may be copied [5] despite the version of a without one appended to it has no uses after append_one due to the assignment. This can be avoided through the usage of inout parameters:

func append_one_in_place(a: inout [Int]) {
a.append(1)
}
var a = [1, 2, 3]
append_one_in_place(&a)

will continue this as it will be quite long if i include in single doc. There other important factors for performance while working with powerful tools like generics which i will cover in next([part2]) discussion.

Thanks in advance. Please give your support if you liked the content.

--

--

Shobhit Gupta

Sr iOS Engineer, Bharti Airtel Limited, Technology Lover