This is expected to an extent: actors are much higher-level than simple locks and rely on Swift Concurrency runtime to work, which includes some scheduling logic on top of OS threads. In that, they are more comparable to GCD queues.
You should not really expect actors to demonstrate comparable performance in tasks such as simply incrementing an integer from a different isolation domain, as the runtime will have to constantly switch contexts, which is way more expensive than the operation itself.
Part of the problem is that testStateHolderActor()
runs on the so-called generic executor, while the actor has its own executor, and the runtime has to switch in each iteration. You can modify your logic to only hop off the actor once:
func testStateHolderActor() async {
let e = measureSH()
let actor = StateHolderActor()
var sum = 0
actor.onNewValueReceived = { val in
sum += val
}
func run(actor: isolated StateHolderActor) async {
for i in 0 ..< iterations {
actor.handleValueRecieved(1)
}
}
await run(actor: actor)
e()
}
You will find much more success with actors for tasks where you have to isolate larger stateful systems, where it becomes increasingly more cumbersome to set up locking properly and there's a need to support async
operations by design, such as network I/O.