# 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, no downcast. Direct method calls that the compiler can inline. Single-threaded only. - **Actix**: Vtable dispatch via `Box>`. `Addr` 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` 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)