Swift Performance — Dynamic Dispatch;
Method dispatch is the algorithm used to decide which method should be invoked in response to a message. Compile time dispatches are called static dispatches and dynamic dispatch happen at runtime with the help of a virtual table.
Dynamic dispatch and its effect on performance
Dynamic dispatch uses an array of function pointers for each method in the class declaration. Most languages refer to this as a “virtual table”. Every subclass has its own copy of the table with a different function pointer for every method that the class has overridden. As subclasses add new methods to the class, those methods are appended to the end of this array. This table is then consulted at runtime to determine the method to run.
This technique increases language expressivity at the cost of a constant amount of runtime overhead for each indirect usage. I will demonstrate this with an example. Let’s consider the following class Address.
class Address {
var street = ""
var city = ""
var state = ""; enum AddressAttributes {
case Street
case City
case State
} func update(value: String, attribute: AddressAttributes){
switch attribute {
case .Street:
street = value;
case .City:
city = value;
case .State:
state = value;
}
} func updateAttribute(value: String, attribute: AddressAttributes){
update(value: value, attribute: attribute)
}
}var address = Address()
address.update(value: "Pier 39", attribute: .Street)
- Call updateAttribute on address.
- Call update on address.
- Get the property street.
When updateAttribute() is called on address, it reads the dispatch table for object OxAA. Then it jumps to Ox1 to invoke updateAttribute().
The dynamic calls are necessary because a subclass of Address might override updateAttribute() or update(). Additionally, indirect calls also prevent many compiler optimizations, making the indirect call even more expensive. So reducing unwanted dynamic dispatches can improve the overall performance.
Use Final
The final keyword is a restriction on a class, method, or property that indicates that the declaration cannot be overridden. This allows the compiler to safely elide dynamic dispatch indirection. I have measured the differences with and without the final keyword and used a for loop to trigger an arbitrary number of indirections. This example declares the entire class as final.
final class Address {
...
...
}var address = Address()for _ in 0..<10000000 {
address.update(value: “Pier 39”, attr: .Street)
address.update(value: “San Fransciso”, attr: .City)
address.update(value: “CA”, attr: .State)
}
Without final: 15.7s
With final: 13.5
If you don’t have to override certain class methods or properties, always use the private keyword to limit the visibility to the current class and remove indirect calls for methods and property accesses.
Another way to improve is to enable whole module configs in Xcode to infer final on internal declarations.
Tip: Use Whole module only for Release configuration, or it will result in slower build times.
From Apple Docs
Declarations with internal access (the default if nothing is declared) are only visible within the module where they are declared. Because Swift normally compiles the files that make up a module separately, the compiler cannot ascertain whether or not an internal declaration is overridden in a different file. However, if Whole Module Optimization is enabled, all of the module is compiled together at the same time. This allows the compiler to make inferences about the entire module together and infer final on declarations with internal if there are no visible overrides.
Thanks for reading :)