There are a sufficient number of ways to increase the speed of a project at runtime. Let’s look at the most popular of them:
Under the hood, any new class will use dynamic (table) dispatch. This is a price for the ability to be inherited, but if the class is not inherited, you can save a significant amount of resources. The final
prefix will turn your class into a statically dispatched object.
Before optimization:
// parent class with dynamic table dispatch
class A {}
// child class with dynamic table dispatch
class B: A {}
After optimization:
// child class with static dispatch
final class B: A {}
Parent class A:
A similar approach is used for class methods, but there are several options available:
private
.final
.
Both prefixes will turn your method into a statically dispatched method but with different scopes.
Before optimization:
// method with dynamic table dispatch
func method() {}
After optimization:
// private method with static dispatch
private func method() {}
// method with static dispatch cannot be override
final func method() {}
One of the most curious and rarely used types of optimization. Inline optimization allows you to copy code calculations directly to the place where a given function is called without calling a method marked with inline, which reduces the number of method calls and works faster than static dispatch.
Among the pitfalls, in order to force the compiler to call a method with inline optimization — it is necessary to mark the method not only @inlineable
but also @inline(__always)
It is important not to mark methods that spend a large amount of resources on copying with these prefixes; there is a risk of filling the cache of all processor levels with calculations of these methods and, as a result, getting slower instead of speeding up.
Before optimization:
// test method with multiple calling
func testMethod() {}
After optimization:
// inlinable test method with multiple calling
@inlinable
@inline(__always)
func testMethod() {}
Under the hood, protocols in Swift can be implemented for reference types and value types. Due to the fact that the memory management mechanism for reference types and value types are different, you can significantly speed up the compiler by limiting protocol adoption to reference types only.
Before optimization:
// can be adopted to reference types and value types,
// but value types adoption is unnecessarily
protocol Implementable {}
After optimization:
// can be adopted to reference types only
protocol Implementable: AnyObject {}
Array
was originally intended not only as a collection type for Swift, allowing elements of the same type to be stored sequentially, but also as a collection type providing compatibility with NSArray
.
In case the array is intended to store non-ObjC data types, it is recommended to use ContagiousArray
for more efficient operation of the array.
Before optimization:
// creation array with NSArray interop
let array: Array<Int> = [1, 2, 3]
After optimization:
// creation array without NSArray interop
let fastArray: ContiguousArray<Int> = [1, 2, 3]
Examples of speed boost:
Array
:
ContagiousArray
:
Also published here.
Don’t hesitate to contact me on