lambdaclass / lambdaclass/spawned

perf: performance audit findings for 0.5.0+

Open
#156 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Rust
Stars
59
Forks
5
PR merge metrics
No merged PRs in 30d

Description

Performance Audit

Analysis of the 0.5.0 refactor (#[protocol] + #[actor] macros, Handler<M>, envelope pattern) compared to the 0.4.x enum-based approach. These are optimization opportunities — none are blockers for 0.5.0.

High Impact

1. Threads mode: 100ms polling loop for cancellation — #157

threads/actor.rs uses rx.recv_timeout(Duration::from_millis(100)) to periodically check cancellation_token.is_cancelled(). This causes:

  • Up to 100ms shutdown latency
  • Constant wakeups even when idle (10/sec)
  • CPU waste on idle actors

Fix: Poison-pill approach — send a sentinel through the mailbox channel on stop(), replacing the timeout loop with blocking rx.recv().

Medium Impact

2. Two heap allocations per message (tasks mode)

Each message incurs:

  • Box<dyn Envelope<A>> for type erasure into the mailbox
  • Pin<Box<dyn Future>> from Envelope::handle() returning a boxed future

The old 0.4.x code used ActorInMsg<A> enum with associated types — zero extra heap allocs. The new design trades this for flexibility (unlimited message types via Handler<M>).

Possible mitigations: SmallBox for stack-allocating small envelopes, arena/bump allocators for batch allocation, monomorphization for hot paths.

3. Box<dyn Envelope> per message (threads mode)

Same envelope boxing as tasks, but no future boxing (sync handlers). Still one heap alloc per message that didn't exist in 0.4.x.

4. No message batching

The run loop processes one message at a time, checking cancellation between each. Draining multiple messages before yielding could reduce overhead under high throughput. Marginal for tasks mode (tokio batches internally), more impactful for threads.

Low Impact

5. CancellationToken uses SeqCst atomics

rt/src/threads/mod.rs uses AtomicBool with Ordering::SeqCst. Acquire/Release would suffice and is slightly cheaper on ARM. Negligible on x86.

6. Extra mpsc channel per timer (threads mode)

Each send_after/send_interval creates a dedicated mpsc::channel() for cancellation. Could reuse AtomicBool instead.

7. Unbounded mailbox — no backpressure

Both modes use unbounded channels. Same as 0.4.x and Erlang, but bounded channels with configurable capacity could be a future option.

Priority
  • 0.5.1: Item 1 (threads polling loop) — #157
  • Future: Items 2-4 (envelope/future allocation — profile first)
  • Nice to have: Items 5-7

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with #157 and read threads/actor.rs, especially the recv_timeout loop; rt/src/threads/mod.rs contains the cancellation atomic mentioned in the audit. Profile or benchmark the listed hot paths before choosing an item, and consider the work done when the selected optimization is implemented with evidence that its targeted overhead or latency improved.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
performance
Issue type
Refactor
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.