4.4 KiB
4.4 KiB
Cycle 5: Load-Aware Actor Placement + Work Stealing Research — Development History
Commit:
7d00e65· 6 files · 184 insertions, 13 deletions
Motivation
With fairness (Cycle 1), thread parking (Cycle 3), and shutdown (Cycle 4) resolved, the next bottleneck was actor placement. Swactor used blind round-robin to assign actors to workers — ignoring current load. If actors have unequal workloads, round-robin produces persistent imbalance. This cycle also included deep research into work stealing to decide whether full actor migration was worthwhile.
Competitor Analysis: Work Stealing Deep Dive
| Aspect | Tokio | Go | BEAM | ForkJoinPool |
|---|---|---|---|---|
| Queue | Fixed 256-slot ring | 256-slot ring + runnext | Per-priority linked | Growable array deque |
| Steal granularity | Half victim's queue | Half victim's runq | Individual processes | One task at a time |
| LIFO fast-path | Dedicated slot (3-use cap) | runnext (stealable 4th try) | None | Owner pops from top |
| Global queue | Mutex intrusive list | Checked 1/61 ticks | Per-priority migration | Even-indexed queues |
| Searcher limit | N/2 workers | GOMAXPROCS/2 | N/A (proactive migration) | Idle stack in ctl |
| Balance strategy | Reactive steal | Reactive steal | Proactive migration + reactive | Reactive scan |
Key Patterns Discovered
- LIFO slot — every runtime has one; improves cache locality by running the recipient immediately after the sender. Tokio caps at 3 consecutive uses to prevent starvation.
- Steal-half — Tokio and Go both steal half the victim's queue, amortizing cross-thread coordination overhead.
- N/2 searcher limit — both Tokio and Go cap concurrent searchers to prevent thundering herd (O(N²) cache-line bouncing).
- BEAM's migration — unique dual approach: reactive stealing when idle + proactive migration via periodic
check_balance().
Feasibility for Swactor
- Full actor migration: Mechanically possible (ActorSlot is
Send), but has a 1-tick message loss window during migration and requires push-based donation (ActorPoolis notSync→ no pull stealing) - Message stealing without actors: Impossible — the actor IS the state; messages without the actor are meaningless
- Decision: Load-aware placement over work stealing — zero correctness risk, handles the primary imbalance source (uneven spawn distribution), full work stealing deferred
Implementation
Placement::next_worker()now reads per-worker stats (num_actors+mailbox_depth)- Selects the worker with lowest combined load
- Scan starts from a rotating position → round-robin fallback when all stats are equal (initial burst, before first tick publishes stats)
- O(N) relaxed atomic loads per spawn — trivial for N ≤ 8 workers
Key files modified: src/delivery.rs, tests/runtime_api.rs, benches/runtime_benchmarks.rs
Design Decisions
- Load-aware placement instead of work stealing — zero message loss risk, no ordering changes, trivial implementation cost. Handles the #1 source of imbalance: uneven spawn distribution.
- Combined metric (actors + depth) — neither actor count alone nor mailbox depth alone captures load accurately. Combined metric approximates total pending work per worker.
- Relaxed atomics for stat reads — stats are advisory (best-effort), so relaxed ordering is sufficient. No need for acquire/release which would add synchronization cost.
- Round-robin fallback — before the first tick, all workers report zero stats. Falling back to round-robin ensures even initial distribution rather than always picking worker 0.
- Full work stealing deferred — would require migration channels, address map coordination, forwarding tombstones, and a message loss window. Benefit uncertain for N ≤ 8 workers.
Tests Added
3 new tests (57 → 60 total):
load_aware_placement_prefers_lighter_worker— imbalanced load biases spawn toward the lighter workerload_aware_placement_single_worker_degrades_gracefully— single-thread mode works correctlyload_aware_placement_falls_back_to_round_robin_on_fresh_runtime— even distribution before ticks produce stats
Benchmark added: placement/spawn_under_load (2-thread and 4-thread variants)
Result
- 60 tests pass
- All workspace crates compile
- Comprehensive work-stealing research documented for future reference