Skip to content

Eliminate hot-path allocations in Map.Get, Map.Without, Set.Has - #88

Merged
marcelocantos merged 1 commit into
masterfrom
marcelo/eliminate-read-path-allocs
Mar 7, 2026
Merged

Eliminate hot-path allocations in Map.Get, Map.Without, Set.Has#88
marcelocantos merged 1 commit into
masterfrom
marcelo/eliminate-read-path-allocs

Conversation

@marcelocantos

Copy link
Copy Markdown
Collaborator

Summary

  • Cache DefaultNPEqArgs per type to avoid fresh *EqArgs allocation per call
  • Add string specializations in resolveHashFunc and EqualFuncFor to avoid boxing through any(x)
  • Add GetSeededHashFunc for non-boxing seeded hashing of map keys
  • Rewrite mapEntryHashFunc to hash keys directly instead of boxing the mapEntry struct through the Hashable interface
  • Add Tree.GetWith/WithoutWith accepting pre-built EqArgs
  • Map.Get/Without use cached specialized EqArgs with GetWith/WithoutWith

Benchmarks (arrai, Apple M4 Max)

Operation Before After
Map[string,Value].Get 6 allocs, ~120 ns 0 allocs, ~26 ns
TupleGet 6 allocs, ~120 ns 0 allocs, ~27 ns
Set[string].Has 4 allocs 0 allocs, ~20 ns
Set[Value].Has 4 allocs 1 alloc, ~45 ns

The remaining 1 alloc on Set[Value].Has is the equalEqualer boxing path for interface types — deferred as it's low-impact (interface-to-any boxing is cheap).

Test plan

  • All frozen tests pass (go test ./...)
  • All arrai tests pass (go test ./...)
  • Benchmarks verified with -count=5
  • CI green

🤖 Generated with Claude Code

The generics migration introduced 6 allocs/call on Map.Get and 4 on
Set.Has due to Go's GCShape stenciling boxing struct types through
any(x). This change eliminates all allocations on read paths for
concrete key types (string, int, etc.) and reduces interface key
types to at most 1 alloc.

Key changes:
- Cache DefaultNPEqArgs per type (was allocating fresh EqArgs per call)
- Add string specializations in resolveHashFunc and EqualFuncFor to
  avoid boxing through any(x)
- Add GetSeededHashFunc for non-boxing seeded hashing of map keys
- Rewrite mapEntryHashFunc to hash keys directly instead of boxing
  the mapEntry struct through the Hashable interface
- Add Tree.GetWith/WithoutWith accepting pre-built EqArgs
- Map.Get/Without use cached specialized EqArgs with GetWith/WithoutWith

Benchmarks (arrai, Apple M4 Max):
  Map[string,Value].Get: 6 allocs ~120ns → 0 allocs ~26ns (4.6x)
  TupleGet:              6 allocs ~120ns → 0 allocs ~27ns (4.5x)
  Set[string].Has:       4 allocs        → 0 allocs ~20ns
  Set[Value].Has:        4 allocs        → 1 alloc  ~45ns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@marcelocantos
marcelocantos merged commit 5b2842e into master Mar 7, 2026
4 checks passed
@marcelocantos
marcelocantos deleted the marcelo/eliminate-read-path-allocs branch March 7, 2026 06:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant