refactor(appstate): scan instead of HashSet for index-mac dedup in the patch path - #865
Conversation
…detection `detect_duplicate_index_in_patch` deduped index_macs with two `HashSet<&[u8]>`. The keys are HMAC outputs (uniformly random), so SipHash buys no distribution benefit over a trivial compare — it only costs the per-key hash setup and a table allocation, which `bench_process_patch_50_validated` showed via `RandomState::hash_one` + `fallible_with_capacity`. Swap both sets for linear-scan `Vec<&[u8]>`, the same trade-off already validated for the sibling `collect_unique_index_macs` (#856), where HashSet measured 6-120% worse at the patch sizes seen in practice. Set and Remove stay deduped independently, so the existing semantics (`process_patch_rejects_duplicate_set_index` and `process_patch_allows_same_index_across_set_and_remove`) are unchanged. Zero new dependencies (no fast-hash crate), matching the recent dependency trimming. CI/CodSpeed measure the delta on bench_process_patch_50_validated. https://claude.ai/code/session_01XHsbPwjaCRDHDL69HbEgR8
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughSummary by CodeRabbit
WalkthroughTwo appstate patch-processing functions replace ChangesAppState Patch Index Tracking: HashSet → Vec
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📦 Binary size report
.text per crate
Top movers (cargo-bloat attribution)
Baseline: |
`HashState::update_hash` tracked REMOVE index_macs in a `HashSet<&[u8]>` to gate the SET-also-REMOVEd double-subtract. Same situation as the in-patch duplicate check: the keys are HMAC outputs (uniformly random), so SipHash buys nothing over a byte compare. Swap it for a linear-scan `Vec<&[u8]>`. Only `.contains()` is queried, so an unconditional push is membership- equivalent to the set. The deliberate `in_patch` value-lookup `HashMap` (O(1) replacing an O(n^2) reverse scan) is intentionally left as-is, and the key-id dedup in `collect_key_ids_from_patch_list` is a different, tiny-N path. https://claude.ai/code/session_01XHsbPwjaCRDHDL69HbEgR8
What
Two
HashSet<&[u8]>overindex_macblobs in the patch-validation path are swapped for linear-scanVec<&[u8]>:detect_duplicate_index_in_patch(processor.rs) — the in-patch duplicate-index guard (Set/Remove deduped independently).HashState::update_hash'sremoved_in_patch(hash.rs) — the membership set that gates the SET-also-REMOVEd double-subtract.Why
The keys are
index_macblobs — HMAC outputs, i.e. uniformly random bytes. SipHash's distribution/DoS resistance buys nothing over a trivial byte compare; it only costs the per-key hash setup and a table allocation. This is the same trade-off the project already validated for the siblingcollect_unique_index_macsin #856, where a HashSet measured 6–120% worse than a linear scan at the patch sizes seen in practice (N≈10–50). Applying the same scan keeps the three functions consistent, and uses zero new dependencies (no fast-hash crate — consistent with the recent dependency trimming, e.g. #860).Honest measurement — this is NOT a measurable speedup
I profiled it with the CodSpeed MCP before claiming anything. CodSpeed reports no performance change across all 172 benchmarks, and a direct run-to-run compare came back at −0.05% overall, attributed to an environment difference (AVX-512 flags), not the code. In the
bench_process_patch_50_validatedflamegraph the dedup doesn't even surface as a ≥1% line: the 2 ms benchmark is crypto-bound (SHA-256compress25666%, lthash HKDF 59%,decode_record32%, MAC validation). Deduping ~50 random 32-byte MACs is microscopic next to ~15 SHA compressions.So this is reframed from
perftorefactor: the concrete, measured win is binary size — the prior binary-size report showedwacore_appstate.textdropping −1.1 KiB (−3.03%) from dropping the monomorphized HashSet/SipHash machinery — plus one fewer allocation per patch and simpler code. No latency claim attached.Deliberately left as-is
in_patchvalue-lookupHashMap(processor.rs) — an intentional O(1) map replacing an O(n²) reverse scan; converting it would regress.collect_key_ids_from_patch_list'sseen(decode.rs) — dedups key IDs (not MACs), tiny N, off the hot path.Correctness
Behaviour is byte-for-byte identical. Set and Remove stay deduped independently, and
removed_in_patchis only queried via.contains(), so unconditional push is membership-equivalent. Existing tests unchanged and cover both paths:process_patch_rejects_duplicate_set_index,process_patch_allows_same_index_across_set_and_remove, plus theupdate_hashindex-mode tests.Verification
cargo fmt --all— clean (run locally)https://claude.ai/code/session_01XHsbPwjaCRDHDL69HbEgR8