Repository navigation
Specialize membership index by concrete key type - #2
Merged
Merged
Conversation
The membership index was a single map[any]struct{}. A generic-any map hashes
every key through the interface's type descriptor — roughly twice the cost of a
map keyed by a concrete comparable type — and that interface hashing was the
dominant residual cost on build and union, the two ops still losing to MRI+YJIT.
Most real Sets are homogeneous, so the index now keeps a concrete-typed map for
the common key types (all Go int, all int64, all string) and falls back to the
generic map only for mixed / other-typed sets. The new membership type carries a
kind discriminator plus the active map; hot paths (has / add / putNew / del) are
a single predictable branch on kind, then a concrete-typed map op. clone() uses
maps.Clone on the active typed map, keeping Union's fast bulk copy.
Exact Ruby semantics are preserved. Each fast path is gated on ONE concrete type,
so int(1), int64(1), 1.0 and "1" remain four distinct members: the first key of a
foreign concrete type promotes the whole index to the generic map, re-boxing every
existing key under its original type so nothing is lost or coalesced. Insertion
order, dedup, delete, the full algebra/predicate surface and the MRI oracle are
unchanged.
Measured (Apple M4 Max, ruby 4.0.5 +PRISM/+YJIT, best-of, ns/op):
op before(vsMRI) after(vsMRI) after(vsYJIT)
build-1000 1.87x 1.35x 1.33x (floor: order slices)
union-1000 1.57x 1.07x 1.06x (parity)
intersection 1.11x 0.78x 0.82x (beats YJIT)
membership 0.41x 0.28x 0.74x (beats YJIT)
build/union residual is the two parallel insertion-order slices Go must maintain
because Go maps are not insertion-ordered — work MRI's ordered Hash provides for
free (the typed map dedup alone is already ~MRI parity). intersection and
membership now beat both MRI and YJIT.
Tests: homogeneous fast paths (int/int64/string), generic fallback, every type
migration (incl. a set that starts homogeneous then gains a mixed-type member),
order preservation across migration, delete/clone/putNew per kind. 100% statement
coverage; race-clean; MRI oracle green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The membership index was a single
map[any]struct{}. A generic-anymap hashes every key through the interface's type descriptor — roughly 2x the cost of a map keyed by a concrete comparable type — and that interface hashing was the dominant residual cost onbuildandunion, the two ops still losing to MRI+YJIT.Most real Sets are homogeneous, so the index now keeps a concrete-typed map for the common key types (all Go
int, allint64, allstring) and falls back to the generic map only for mixed / other-typed sets. A newmembershiptype carries akinddiscriminator plus the active map; the hot paths (has/add/putNew/del) are a single predictable branch onkind, then a concrete-typed map op.clone()usesmaps.Cloneon the active typed map, preservingUnion's fast bulk copy.Exact Ruby semantics preserved
Each fast path is gated on one concrete type, so
int(1),int64(1),1.0and"1"remain four distinct members. The first key of a foreign concrete type promotes the whole index to the generic map, re-boxing every existing key under its original type so nothing is lost or coalesced. Insertion order, dedup, delete, the full algebra/predicate surface and the MRI oracle are unchanged.Measured (Apple M4 Max,
ruby 4.0.5 +PRISM/+YJIT, best-of, ns/op)The
build/unionresidual is the two parallel insertion-order slices Go must maintain because Go maps are not insertion-ordered — work MRI's ordered Hash provides for free (the typed-map dedup alone is already ~MRI parity).intersectionandmembershipnow beat both MRI and YJIT.Tests
Homogeneous fast paths (int/int64/string), generic fallback, every type migration (including a set that starts homogeneous then gains a mixed-type member), order preservation across migration, delete/clone/
putNewper kind. 100% statement coverage;-raceclean; MRI oracle green.🤖 Generated with Claude Code