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>
62 lines
3.7 KiB
Markdown
62 lines
3.7 KiB
Markdown
# Progress Log
|
|
|
|
## Current Stage: Cycle 20 — Hot-Path Performance (Identity Hashing)
|
|
|
|
### Status: COMPLETE
|
|
|
|
### Research
|
|
- **Stakker**: Closure-based dispatch. `call!` macro generates `FnOnce` closures pushed to a flat byte Vec queue. No HashMap, no Box<dyn Any>, 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
|
|
1. **Custom `Hash` for ActorAddress** (`src/actor.rs`) — only hashes first 8 bytes instead of 32. All HashMaps using ActorAddress benefit automatically.
|
|
2. **Identity hasher** (`src/delivery.rs`) — `AddrHasher`/`AddrBuildHasher` that passes the 8-byte hash value through as the bucket index directly, skipping SipHash.
|
|
3. **Hot-path HashMap replacement** — `AddrMap<V>` type alias used in:
|
|
- `AddressMap.inner` (delivery.rs) — on every send
|
|
- `ActorPool.actors` (worker.rs) — on every deliver and tick_all
|
|
- `InboxRegistry.senders` (delivery.rs)
|
|
- `MonitorRegistry.monitors` (delivery.rs)
|
|
- `NameRegistry.reverse` (delivery.rs)
|
|
- `GroupRegistry.memberships` + `AddrSet` for group member sets (delivery.rs)
|
|
- `TransportRouter.routes` (transport.rs)
|
|
4. **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 hasher
|
|
- `ring_routing_unchanged_after_hasher_optimization` — 100-actor chain, verifies address_map correctness
|
|
- `stop_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 replacements
|
|
- `src/worker.rs` — ActorPool.actors AddrMap; stop_requests optimization
|
|
- `src/transport.rs` — TransportRouter.routes AddrMap
|
|
- `Cargo.toml` — Added hasher_benchmarks bench entry
|
|
- `benches/hasher_benchmarks.rs` — New: component-level microbenchmarks
|
|
- `tests/runtime_api.rs` — 3 new behavioral tests
|
|
- `CLAUDE/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_dispatch` pattern for avoiding `dyn Any` downcast (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_hasher` public API for users? (No — internal optimization only)
|