swactor/CLAUDE/notes/progress.md
Developer e91308720b feat: identity hashing for ActorAddress hot-path optimization (Cycle 20)
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>
2026-02-12 18:01:30 +00:00

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)