Performance: Actor vs queue vs lock
I do a lot of performance and instrumenting work and I’ve found Peter Steinberger’s post here very useful when comparing lock alternatives.
As I worked with
async/await and actors more and more this summer, I thought it’d be nice to put together a short post offering some basic benchmarking of actors vs. the existing synchronization mechanisms.
Benchmarking depends heavily on the system, temporary conditions, and more. As with any amateur benchmarks, take the numbers in this post with a grain of salt.
The Benchmark Setup
I’ve prepared a super-duper simple SwiftUI app that increments a counter 10,000 times. This is a very limited use case but since a counter is often the first example used to teach about synchronization — I’ll go with it.
The project source is available here: https://github.com/icanzilb/ActorBench.
This is my setup to build and run the benchmark:
- Xcode Version 13.0 (13A233)
- a Release configuration build
- no debugger attached
- on my iPhone SE device running iOS 15.0(19A346).
Running the Benchmarks
The app has a very simple UI that allows me to manually start each benchmark in turn:
actor counter uses very minimal code:
The actor serializes calls to
increment() and protects
counter from data races. Calling
increment() 10,000 times on my device gives me an average of:
Duration: 0.132 seconds
And now, the code for the same counter using a
Here, I use a serial
DispatchQueue to safely and synchronously increment the counter. The averaged result for 10,000 increments:
Duration: 0.016 seconds
Using Unfair Lock
Finally, here’s the same counter, but using
os_unfair_lock_s which is the latest and greatest lock on Apple’s platforms:
To prevent data-races, this code locks and unlocks the lock for the duration it needs exclusive access to the counter. The averaged result on my device is:
Duration: 0.009 seconds
The results are pretty straightforward, with
os_unfair_lock being the clear winner for the given performance benchmark:
As mentioned earlier, take the results with a grain of salt. Actors do much more than being simple locks do. That’s why an actual lock was much more performant in this benchmark.
Actors offer safety and design clarity but, naturally, they aren’t the ultimate solution to all problems. As always, use the right tool for the task — for locking, the right tool is a lock 🔐😊.
Thanks for reading and see you next time 👋🏽
Where to go from here?
Interested in the new
async/await Swift syntax? Hit me up on twitter at https://twitter.com/icanzilb.