Benchmarking Swift Locking APIs
When designing concurrent code in Swift, you might wonder which API to pick among the diversity of available choices. In this article we will benchmark performance of most notable Apple locking APIs and suggest best options based on their characteristics.
Locking APIs and Atomicity
We’ve already covered major locking APIs as well as concurrent programming concepts in Atomic Properties in Swift, so make sure you’ve checked this article before moving forward.
Sampling Data
First off, let’s describe sampling data and the way it has been collected.
API under test | Name on chart |
---|---|
NSLock |
NSLock |
pthread_mutex_t |
Mutex |
pthread_rwlock_t |
Read-write lock |
os_unfair_lock_s |
Spinlock |
DispatchQueue |
Dispatch Queue |
OperationQueue |
Operation Queue |
To benchmark each API a class has been implemented with a critical section in its setter and getter.
Source code
Main benchmark function:
To compensate deviations of individual benchmark iterations, each data sample is calculated 100 times and an average value is taken.
Whole app source code can be found here: https://github.com/V8tr/AtomicBenchmark. It implements an abstraction over the above two methods, runs test samples and exports statistics to a CSV file.
Benchmarking Getters
Locks have roughly equal performance, with NSLock
being a bit slower than the others.
OperationQueue
is way slower than DispatchQueue
.
DispatchQueue
is 7-8 times slower than locks, OperationQueue
is ~20 times slower than the dispatch queue and 140-160 times slower than locks.
Benchmarking Setters
Same as with getters, all locks have roughly equal performance and NSLock
is a bit slower than the rest. Variance of statistic is higher, comparing to the same calculations for getters.
Comparing to getters, OperationQueue
falls behind DispatchQueue
even more.
DispatchQueue
is 3-4 times slower than locks, OperationQueue
is ~70 times slower than the dispatch queue and 220-250 times slower than locks.
Comparing Setters vs. Getters
DispatchQueue
and OperationQueue
have considerable variance of setter vs. getter performance, locks are approximately equal.
Summary
Based on the benchmark results, DispatchQueue
must be your best choice for creating a critical section in your code.
Under 10000 calculations it performs almost identical to locks, while providing higher-level and thus less error-prone API.
If for some reason the block-based locking nature of DispatchQueue
is not what you need, I’d suggest to go with NSLock
. It’s a bit more heavyweight than the rest of the locks, but this can be neglected.
Pthread locks are usually a bad choice due to considerably complex configuration and some usage nuances, as highlighted in Atomic Properties in Swift.
Thanks for reading!
If you enjoyed this post, be sure to follow me on Twitter to keep up with the new content. There I write daily on iOS development, programming, and Swift.