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

Benchmarking Swift Locking APIs - 2

Locks have roughly equal performance, with NSLock being a bit slower than the others.

Benchmarking Swift Locking APIs - 3

OperationQueue is way slower than DispatchQueue.

Benchmarking Swift Locking APIs - 1

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

Benchmarking Swift Locking APIs - 5

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.

Benchmarking Swift Locking APIs - 6

Comparing to getters, OperationQueue falls behind DispatchQueue even more.

Benchmarking Swift Locking APIs - 4

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

Benchmarking Swift Locking APIs - 7


Benchmarking Swift Locking APIs - 8


Benchmarking Swift Locking APIs - 9


Benchmarking Swift Locking APIs - 10


Benchmarking Swift Locking APIs - 11


Benchmarking Swift Locking APIs - 12

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.


I’d love to meet you in Twitter: @V8tr. And don’t forget to share this article if you find it useful.