Replace SipHash with identity hasher on all hot-path HashMaps keyed by ActorAddress. Since addresses are crypto-random, the first 8 bytes serve as an excellent hash directly. Microbenchmarks show 1.9-4.6x lookup speedup depending on map size. - Custom Hash impl for ActorAddress (8-byte write_u64 instead of 32) - AddrHasher/AddrBuildHasher identity hasher in delivery.rs - AddrMap<V>/AddrSet type aliases used in 7 HashMap sites - Stop-requests is_empty() short-circuit in tick_all inner loop - 3 new behavioral tests, component microbenchmarks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3.7 KiB
3.7 KiB
Progress Log
Current Stage: Cycle 20 — Hot-Path Performance (Identity Hashing)
Status: COMPLETE
Research
- Stakker: Closure-based dispatch.
call!macro generatesFnOnceclosures pushed to a flat byte Vec queue. No HashMap, no Box, no downcast. Direct method calls that the compiler can inline. Single-threaded only. - Actix: Vtable dispatch via
Box<dyn EnvelopeProxy<A>>.Addr<A>is a direct channel reference (no HashMap lookup). Custom Vyukov lock-free MPSC queue. Default mailbox capacity 16. - Key insight: Both avoid HashMap entirely by using direct references. Swactor needs HashMaps for location-transparent 32-byte addresses (multi-worker routing + transport). The optimization is to minimize HashMap cost, not eliminate it.
- Full analysis:
CLAUDE/notes/dispatch_comparison.md
Implementation
- Custom
Hashfor ActorAddress (src/actor.rs) — only hashes first 8 bytes instead of 32. All HashMaps using ActorAddress benefit automatically. - Identity hasher (
src/delivery.rs) —AddrHasher/AddrBuildHasherthat passes the 8-byte hash value through as the bucket index directly, skipping SipHash. - Hot-path HashMap replacement —
AddrMap<V>type alias used in:AddressMap.inner(delivery.rs) — on every sendActorPool.actors(worker.rs) — on every deliver and tick_allInboxRegistry.senders(delivery.rs)MonitorRegistry.monitors(delivery.rs)NameRegistry.reverse(delivery.rs)GroupRegistry.memberships+AddrSetfor group member sets (delivery.rs)TransportRouter.routes(transport.rs)
- Stop-requests optimization (worker.rs) —
is_empty()short-circuit before linear scan in inner message loop
Microbenchmark Results (reliable, same-process A/B)
| Operation | SipHash | Identity | Speedup |
|---|---|---|---|
| Hash | 1.35 ns | 0.67 ns | 2.0x |
| Lookup/100 | 29.8 ns | 15.5 ns | 1.9x |
| Lookup/1000 | 38.0 ns | 8.2 ns | 4.6x |
| Insert 1000 | 48.2 µs | 21.1 µs | 2.3x |
Note: End-to-end benchmarks unreliable in sandbox (55% variation between identical runs). Microbenchmarks confirmed significant hash/lookup improvement.
Tests
- 143 behavioral tests pass (140 existing + 3 new)
- 7 proptest pass
- New tests:
many_actors_all_receive_correct_messages— 200 actors, verifies no misrouting from identity hasherring_routing_unchanged_after_hasher_optimization— 100-actor chain, verifies address_map correctnessstop_self_with_pending_messages_still_works— verifies stop_requests optimization correctness
Files Modified
src/actor.rs— Custom Hash impl for ActorAddress (8-byte)src/delivery.rs— AddrHasher, AddrBuildHasher, AddrMap, AddrSet types; 5 HashMap replacementssrc/worker.rs— ActorPool.actors AddrMap; stop_requests optimizationsrc/transport.rs— TransportRouter.routes AddrMapCargo.toml— Added hasher_benchmarks bench entrybenches/hasher_benchmarks.rs— New: component-level microbenchmarkstests/runtime_api.rs— 3 new behavioral testsCLAUDE/notes/dispatch_comparison.md— New: Stakker/Actix/Swactor analysis
Next Steps
- Profile the
Box::new(msg)allocation cost — SmallBox/inline storage could eliminate heap alloc for small messages - Investigate VecDeque mailbox alternative (slab-allocated ring buffer)
- Consider
enum_dispatchpattern for avoidingdyn Anydowncast (would require API changes) - Benchmark on dedicated hardware (sandbox too noisy for reliable end-to-end measurement)
Open Questions
- Is 8 bytes sufficient for the identity hash? (Yes — 2^64 from crypto-random bytes)
- Should we provide a
with_hasherpublic API for users? (No — internal optimization only)