About
Slacktor is an extremely performant actor library. It supports no-std, has only one dependency, and is extremely simple and portable. In optimal conditions, it runs around 700 million messages/second to a single actor with no other dependencies, as seen in the simple example (which does use the rand crate just to force the compiler to not optimize the code away). Because Slacktor does not handle synchronization, utilizing rayon it is possible to acheive roughly 4.5 billion messages/second on an i9-13900H laptop CPU, as seen in the parallel example.
Slacktor has such a low overhead, that simply changing the u64s in parallel to u32's in parallel_u32 provides a speedup to roughly 9 billion messages/second, and reducing them to a u8 in parallel_u8 increases the speed to 21 billion messages/second. The example no_slacktor is the equivalent of parallel_u8 without Slacktor, and is capable of 22 billion messages/second. Reducing allocations using an iterator method like sum instead of collect, I have measured up to 80 billion messages/second.
Limitations and Disclaimers
Slacktor actors do not have what would be called "contexts" in other actor frameworks, a window to the outside world that allows them to interact with existing actors. It is up to the user to provide this, be it as a RwLock/Mutex of an Arc referencing the Slacktor instance, or through message passing. Slacktor is focused on providing a simple and performant core for actor based systems, with minmal dependencies.
Slacktor doesn't actually use message passing. Instead, it emulates message passing on top of raw function calls. This allows for extremely high performance, maintains the advantages of other actor frameworks, and provides users with complete control over their project structure. You can use Slacktor as much or as little as you want.
How is Slacktor So Fast?
Slacktor doesn't try to handle any synchronization, concurrency, or message passing. Instead, Slacktor provides a simple abstraction over a slab of actors. Message passing is then emulated by calling the message handler as soon as send is called. This allows the compiler to essentially optimize away the entirery of Slacktor down to just a few function calls, while still maintaining the abstraction of message passing.
Benchmarks
On my laptop (i9-13900H, 32GB RAM), the following code outputs roughly 700,000,000 messages/second:
use Instant;
use ;
;
;
Moving the system.get call into the loop drops it to roughly 400,000,000 messages/sec:
// Create a slacktor instance
let mut system = new;
// Create a new actor
let actor_id = system.spawn;
// Time 1 billion messages, appending each to a vector and doing some math to prevent the
// code being completely optimzied away.
let num_messages = 1_000_000_000;
let mut out = Vecwith_capacity;
let start = now;
for i in 0..num_messages
let elapsed = start.elapsed;
println!;
system.kill;
If we remove pushing the values to the vector (and retrieve the actor reference outside of the loop), the rust compiler is able to completely optimize away the loop, and the code finishes executing in 100ns:
// Create a slacktor instance
let mut system = new;
// Create a new actor
let actor_id = system.spawn;
// Get a reference to the actor
let a = system..unwrap;
// Time 1 billion messages, appending each to a vector and doing some math to prevent the
// code being completely optimzied away.
let num_messages = 1_000_000_000;
let start = now;
for i in 0..num_messages
let elapsed = start.elapsed;
println!;
system.kill;
Retrieving the actor reference inside of the loop in this case gives us roughly 600,000,000 messages/second.
The following equivalent code for the Actix framework can handle roughly 400,000 messages/second, and does not allow the Rust compiler to optimize away second loop. I have reduced the number of messages to 1 million, as 1 billion is too much for Actix to handle in a reasonable timeframe.
use Instant;
use *;
;
// Actor definition
;
// now we need to implement `Handler` on `Calculator` for the `Sum` message.
async
All of these tests were run with cargo --release, Cargo version 1.75.0 and rustc version 1.75.0 with lto enabled (to minimal effect).
It is safe to say that Slacktor introduces almost no overhead to any projects that use it.
Additionally, Slacktor is entirely parallelizable, so the following code utilizing Rayon is capable of acheiving roughly 4.5 billion messages per second:
// Create a slacktor instance
let mut system = new;
// Create a new actor
let actor_id = system.spawn;
// Get a reference to the actor
let a = system..unwrap;
// Time 1 billion messages, appending each to a vector and doing some math to prevent the
// code being completely optimzied away.
let num_messages = 1_000_000_000;
let start = now;
let _v = .into_par_iter.map.;
let elapsed = start.elapsed;
println!;
system.kill;
Retrieving the actor reference inside of the loop leads to a lower speed of roughly 3 billion messages/second. The parallel_u8 example is capable of acheiving roughly 21 billion messages per second.