Skip to content

perf(collections): size bulk constructors for load factor so they never resize (#27) - #168

Merged
marius-bughiu merged 1 commit into
mainfrom
fix/issue-27-bulk-ctor-resize
Jun 6, 2026
Merged

perf(collections): size bulk constructors for load factor so they never resize (#27)#168
marius-bughiu merged 1 commit into
mainfrom
fix/issue-27-bulk-ctor-resize

Conversation

@marius-bughiu

Copy link
Copy Markdown
Owner

What & why

Tier-(a) pick: issue #27 (Performance optimizations, priority:high, milestone 1.2.0) — specifically its Optimize resize operations / Reduce allocations in critical paths tasks. (All three 1.2.0 collections — FrozenCelerityDictionary, CelerityMultiMap, SmallDictionary — are already shipped, so this advances the milestone's remaining Performance lane. #27 is an umbrella and stays open; this is one focused, complete unit within it.)

Every hash-table collection's IEnumerable<…>-source constructor documents that the source's Count is used to size the backing storage "so inserts do not resize." That promise was not met: the resize threshold is size × loadFactor, so a table sized to the raw count still tripped a full rehash-and-copy on the last inserts of the bulk fill. Example at the default 0.75 load factor: a 100-pair source sized to 128, threshold 96, then rehashed into 256 near the end — an extra int[]/TValue[] allocation pair and ~95 redundant hashes thrown away.

The fix scales the requested capacity up by 1 / loadFactor in each collection's InitialCapacityForSource helper, so a known-count source lands below the threshold and the bulk build is a single allocation with one hash per entry and zero rehashes.

Scope & safety

  • Applied uniformly to all seven hash-table collections: IntDictionary, LongDictionary, CelerityDictionary, IntSet, LongSet, CeleritySet, CelerityMultiMap (distinct-key fills; duplicate-heavy sources just leave slack, never resize).
  • Plain (capacity, loadFactor) constructor unchangedcapacity still rounds to the next power of two as documented. Only the source constructor gains the headroom, so the two ctors stay consistent for the same capacity value.
  • SmallDictionary is exempt — it has no load factor and no hasher (it linear-scans), so its capacity-verbatim sizing already fills without resizing.
  • Exception ordering preserved (issue IEnumerable<T> constructors throw ArgumentOutOfRangeException instead of ArgumentNullException when source is null and loadFactor/capacity is also invalid #94): the sizing math runs only after ArgumentNullException.ThrowIfNull(source) and is skipped for an out-of-range load factor, which the primary ctor still rejects with ArgumentOutOfRangeException. A non-collection source (unknown count) falls through to the plain capacity exactly as before.

Parity facets

This is a perf fix to existing behaviour, not a new type, so the new-collection parity facets (dedicated test files, new shared-test type rows, dashboard COLLECTIONS/ship-card entries, new docs sections, ROADMAP status flip) do not apply. The facets that do:

Facet Change
Production Load-factor headroom in the 7 InitialCapacityForSource helpers + accurate XML-doc <param> text
Tests BulkConstructorNoResizeTests — hash-call-counting regression suite (10 facts/theory cases)
Benchmark MemoryAllocationBenchmark gains a FromCollection category
Docs docs/performance.md clarified; docs/api/collections.md claim now actually holds
CHANGELOG [Unreleased] → Fixed bullets for fix / tests / benchmark

BulkConstructorNoResizeTests pins the contract the way TryAddProbeCountTests does — by counting IHashProvider.Hash calls. A no-resize build of N distinct keys costs exactly N hashes; a mid-build resize re-hashes placed entries and pushes the count above N. The assertion fails on the pre-fix sizing and passes after it, with a load-factor-scaling theory (0.25/0.5/0.75/0.95) and a non-collection fallback case.

Test plan

  • dotnet build (whole solution) — 0 warnings, 0 errors
  • dotnet test1978 passed, 0 failed on net8.0 (10 new)
  • dotnet build -c Release of Celerity.Benchmarks — clean
  • CI matrix (Windows / Linux / macOS) on the PR
  • AOT publish smoke test (no new generic instantiations; unaffected)
  • On merge to main: the weekly extended-benchmark run refreshes the dev/bench-extended gh-pages dashboard with the new FromCollection rows (core per-PR dashboard unaffected — MemoryAllocationBenchmark is in the extended suite)

Addresses #27.

🤖 Generated with Claude Code

…er resize (#27)

The `IEnumerable<…>`-source constructors of every hash-table collection
document that the source's `Count` is used to size the backing storage "so
inserts do not resize". That promise was not kept: the resize threshold is
`size * loadFactor`, so a table sized to the raw source count still tripped a
full rehash-and-copy on the last inserts of the bulk fill (a 100-pair source
at the default 0.75 load factor sized to 128, threshold 96, and rehashed into
256 near the end).

Each collection's `InitialCapacityForSource` helper now scales the requested
capacity up by `1 / loadFactor`, so a known-count source fits below the
threshold and the bulk build is a single allocation with one hash per entry
and zero rehashes. Applied uniformly to IntDictionary, LongDictionary,
CelerityDictionary, IntSet, LongSet, CeleritySet, and CelerityMultiMap.

The plain `(capacity, loadFactor)` constructor is unchanged (capacity still
rounds to the next power of two). SmallDictionary is exempt (no load factor,
no hasher). The null-source-before-loadFactor-validation ordering (#94) is
preserved: the sizing math runs only after the null check and is skipped for
an out-of-range load factor, which the primary ctor still rejects.

Addresses issue #27 (Optimize resize operations / Reduce allocations in
critical paths).

- src/Celerity/Collections/*: load-factor headroom in the 7 source ctors
- BulkConstructorNoResizeTests: hash-call-counting regression suite pinning
  exactly N hashes (no resize) per collection, a load-factor-scaling theory,
  and the non-collection fallback. 1978 tests green on net8.0 (10 new).
- MemoryAllocationBenchmark: new FromCollection category (Dictionary vs
  IntDictionary vs CelerityDictionary) showing the saved allocation
- docs/performance.md: clarify the source ctor includes load-factor headroom
- CHANGELOG: Fixed section bullets for the fix, tests, and benchmark

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

Coverage

Metric Value
Line 100% (2811/2811)
Branch 100% (956/956)

@marius-bughiu
marius-bughiu merged commit 01ee398 into main Jun 6, 2026
6 checks passed
@marius-bughiu marius-bughiu mentioned this pull request Jun 6, 2026
7 tasks
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

Benchmarks

1 regression ⚠️ vs main, 1 improvement ✅ (rows past ±10% beyond noise).

Highlights

Benchmark This PR StdDev main Δ
CelerityMultiMapBenchmark.CelerityMultiMap_Remove(ItemCount: 100000) 2.34 ms 123.72 μs 2.00 ms +17.1% ⚠️
IntDictionaryBenchmark.IntDictionary_Remove(ItemCount: 100000) 2.51 ms 1.68 ms 6.31 ms -60.2% ✅
Collections (104)
Benchmark This PR StdDev main Δ
CeleritySetBenchmark.HashSet_Add(ItemCount: 1000) 12.85 μs 93.4 ns 12.47 μs +3.0%
IntSetBenchmark.HashSet_Add(ItemCount: 1000) 12.86 μs 69.8 ns 12.46 μs +3.2%
LongSetBenchmark.HashSet_Add(ItemCount: 1000) 13.57 μs 166.9 ns 13.17 μs +3.0%
CeleritySetBenchmark.CeleritySet_Add(ItemCount: 1000) 9.23 μs 144.3 ns 9.17 μs +0.7%
IntSetBenchmark.IntSet_Add(ItemCount: 1000) 9.37 μs 163.1 ns 9.20 μs +1.9%
LongSetBenchmark.LongSet_Add(ItemCount: 1000) 9.63 μs 109.2 ns 9.88 μs -2.5%
CeleritySetBenchmark.HashSet_Add(ItemCount: 100000) 5.20 ms 111.44 μs 5.20 ms +0.1%
IntSetBenchmark.HashSet_Add(ItemCount: 100000) 5.26 ms 137.62 μs 5.25 ms +0.1%
LongSetBenchmark.HashSet_Add(ItemCount: 100000) 5.11 ms 102.80 μs 5.08 ms +0.7%
CeleritySetBenchmark.CeleritySet_Add(ItemCount: 100000) 3.47 ms 22.17 μs 3.49 ms -0.5%
IntSetBenchmark.IntSet_Add(ItemCount: 100000) 3.52 ms 17.61 μs 3.55 ms -1.0%
LongSetBenchmark.LongSet_Add(ItemCount: 100000) 6.80 ms 64.95 μs 6.76 ms +0.5%
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Build(ItemCount: 1000) 166.36 μs 530.7 ns 164.61 μs +1.1%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Build(ItemCount: 1000) 3.44 ms 16.46 μs 3.33 ms +3.5%
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Build(ItemCount: 100000) 29.63 ms 216.52 μs 29.34 ms +1.0%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Build(ItemCount: 100000) 1.62 s 57.83 ms 1.51 s +7.5%
CeleritySetBenchmark.HashSet_Contains(ItemCount: 1000) 4.72 μs 6.6 ns 4.71 μs +0.1%
IntSetBenchmark.HashSet_Contains(ItemCount: 1000) 4.72 μs 5.1 ns 4.71 μs +0.1%
LongSetBenchmark.HashSet_Contains(ItemCount: 1000) 5.08 μs 20.3 ns 5.05 μs +0.4%
CeleritySetBenchmark.CeleritySet_Contains(ItemCount: 1000) 1.92 μs 19.6 ns 1.92 μs -0.2%
IntSetBenchmark.IntSet_Contains(ItemCount: 1000) 1.92 μs 16.9 ns 1.93 μs -0.3%
LongSetBenchmark.LongSet_Contains(ItemCount: 1000) 2.09 μs 4.6 ns 2.09 μs -0.2%
CeleritySetBenchmark.HashSet_Contains(ItemCount: 100000) 1.51 ms 7.77 μs 1.50 ms +0.1%
IntSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.50 ms 4.54 μs 1.50 ms +0.3%
LongSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.57 ms 6.67 μs 1.58 ms -0.3%
CeleritySetBenchmark.CeleritySet_Contains(ItemCount: 100000) 549.87 μs 2.08 μs 546.08 μs +0.7%
IntSetBenchmark.IntSet_Contains(ItemCount: 100000) 496.00 μs 7.21 μs 496.81 μs -0.2%
LongSetBenchmark.LongSet_Contains(ItemCount: 100000) 576.74 μs 11.25 μs 575.01 μs +0.3%
SmallDictionaryBenchmark.Dictionary_Insert(ItemCount: 8) 178.0 ns 16.8 ns 164.7 ns +8.0%
SmallDictionaryBenchmark.SmallDictionary_Insert(ItemCount: 8) 76.0 ns 1.6 ns 76.4 ns -0.5%
SmallDictionaryBenchmark.Dictionary_Insert(ItemCount: 64) 799.6 ns 5.6 ns 814.1 ns -1.8%
SmallDictionaryBenchmark.SmallDictionary_Insert(ItemCount: 64) 1.03 μs 3.9 ns 1.02 μs +1.2%
CelerityDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 14.71 μs 164.0 ns 13.42 μs +9.6%
CelerityMultiMapBenchmark.Dictionary_Insert(ItemCount: 1000) 27.36 μs 144.8 ns 26.50 μs +3.2%
IntDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 14.20 μs 156.7 ns 13.48 μs +5.4%
LongDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 16.01 μs 184.0 ns 14.86 μs +7.7%
CelerityDictionaryBenchmark.CelerityDictionary_Insert(ItemCount: 1000) 9.55 μs 182.6 ns 9.10 μs +5.0%
CelerityMultiMapBenchmark.CelerityMultiMap_Insert(ItemCount: 1000) 38.55 μs 363.0 ns 37.38 μs +3.1%
IntDictionaryBenchmark.IntDictionary_Insert(ItemCount: 1000) 9.28 μs 259.3 ns 8.70 μs +6.7%
LongDictionaryBenchmark.LongDictionary_Insert(ItemCount: 1000) 9.04 μs 155.5 ns 8.53 μs +6.0%
CelerityDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 4.40 ms 77.17 μs 4.38 ms +0.5%
CelerityMultiMapBenchmark.Dictionary_Insert(ItemCount: 100000) 13.97 ms 761.14 μs 13.50 ms +3.5%
IntDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 4.50 ms 73.26 μs 4.42 ms +1.7%
LongDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 5.31 ms 43.25 μs 5.20 ms +2.1%
CelerityDictionaryBenchmark.CelerityDictionary_Insert(ItemCount: 100000) 5.26 ms 55.16 μs 5.29 ms -0.6%
CelerityMultiMapBenchmark.CelerityMultiMap_Insert(ItemCount: 100000) 18.06 ms 62.53 μs 17.68 ms +2.1%
IntDictionaryBenchmark.IntDictionary_Insert(ItemCount: 100000) 5.22 ms 81.46 μs 5.17 ms +0.9%
LongDictionaryBenchmark.LongDictionary_Insert(ItemCount: 100000) 7.62 ms 93.35 μs 7.58 ms +0.6%
SmallDictionaryBenchmark.Dictionary_Lookup(ItemCount: 8) 38.5 ns 0.0 ns 38.5 ns -0.0%
SmallDictionaryBenchmark.SmallDictionary_Lookup(ItemCount: 8) 24.2 ns 0.8 ns 23.4 ns +3.5%
SmallDictionaryBenchmark.Dictionary_Lookup(ItemCount: 64) 309.5 ns 1.0 ns 302.4 ns +2.3%
SmallDictionaryBenchmark.SmallDictionary_Lookup(ItemCount: 64) 957.2 ns 6.8 ns 968.5 ns -1.2%
CelerityDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.75 μs 31.3 ns 4.67 μs +1.6%
CelerityMultiMapBenchmark.Dictionary_Lookup(ItemCount: 1000) 5.60 μs 508.0 ns 5.22 μs +7.2%
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Lookup(ItemCount: 1000) 7.61 μs 13.7 ns 7.61 μs +0.1%
IntDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.60 μs 4.2 ns 4.62 μs -0.4%
LongDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 5.03 μs 42.2 ns 5.04 μs -0.2%
CelerityDictionaryBenchmark.CelerityDictionary_Lookup(ItemCount: 1000) 2.23 μs 32.8 ns 2.21 μs +1.1%
CelerityMultiMapBenchmark.CelerityMultiMap_Lookup(ItemCount: 1000) 2.45 μs 4.1 ns 2.46 μs -0.3%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Lookup(ItemCount: 1000) 82.77 μs 141.0 ns 81.98 μs +1.0%
IntDictionaryBenchmark.IntDictionary_Lookup(ItemCount: 1000) 2.08 μs 6.9 ns 2.08 μs +0.4%
LongDictionaryBenchmark.LongDictionary_Lookup(ItemCount: 1000) 2.29 μs 3.2 ns 8.99 μs -74.6%
CelerityDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.61 ms 9.26 μs 1.60 ms +0.3%
CelerityMultiMapBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.76 ms 2.99 μs 1.82 ms -3.1%
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Lookup(ItemCount: 100000) 1.82 ms 51.26 μs 1.85 ms -1.7%
IntDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.55 ms 4.78 μs 1.48 ms +4.4%
LongDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.59 ms 13.56 μs 1.58 ms +0.4%
CelerityDictionaryBenchmark.CelerityDictionary_Lookup(ItemCount: 100000) 705.34 μs 13.53 μs 697.64 μs +1.1%
CelerityMultiMapBenchmark.CelerityMultiMap_Lookup(ItemCount: 100000) 708.79 μs 29.70 μs 693.48 μs +2.2%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Lookup(ItemCount: 100000) 8.24 ms 70.88 μs 8.31 ms -0.9%
IntDictionaryBenchmark.IntDictionary_Lookup(ItemCount: 100000) 743.08 μs 35.51 μs 685.24 μs +8.4%
LongDictionaryBenchmark.LongDictionary_Lookup(ItemCount: 100000) 657.17 μs 47.99 μs 697.86 μs -5.8%
SmallDictionaryBenchmark.Dictionary_Remove(ItemCount: 8) 1.23 μs 71.2 ns 1.27 μs -3.2%
SmallDictionaryBenchmark.SmallDictionary_Remove(ItemCount: 8) 1.29 μs 214.8 ns 1.27 μs +2.1%
SmallDictionaryBenchmark.Dictionary_Remove(ItemCount: 64) 5.00 μs 701.5 ns 4.34 μs +15.1%
SmallDictionaryBenchmark.SmallDictionary_Remove(ItemCount: 64) 20.93 μs 530.0 ns 20.93 μs +0.0%
CelerityDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 84.36 μs 8.96 μs 80.73 μs +4.5%
CelerityMultiMapBenchmark.Dictionary_Remove(ItemCount: 1000) 29.81 μs 2.54 μs 27.29 μs +9.3%
CeleritySetBenchmark.HashSet_Remove(ItemCount: 1000) 32.18 μs 3.02 μs 28.32 μs +13.6%
IntDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 75.57 μs 5.92 μs 77.63 μs -2.7%
IntSetBenchmark.HashSet_Remove(ItemCount: 1000) 27.75 μs 2.77 μs 25.94 μs +7.0%
LongDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 82.25 μs 6.86 μs 78.99 μs +4.1%
LongSetBenchmark.HashSet_Remove(ItemCount: 1000) 70.83 μs 5.82 μs 72.31 μs -2.0%
CelerityDictionaryBenchmark.CelerityDictionary_Remove(ItemCount: 1000) 152.20 μs 10.84 μs 147.97 μs +2.9%
CelerityMultiMapBenchmark.CelerityMultiMap_Remove(ItemCount: 1000) 145.49 μs 9.75 μs 149.97 μs -3.0%
CeleritySetBenchmark.CeleritySet_Remove(ItemCount: 1000) 141.65 μs 10.18 μs 139.06 μs +1.9%
IntDictionaryBenchmark.IntDictionary_Remove(ItemCount: 1000) 89.59 μs 7.63 μs 86.64 μs +3.4%
IntSetBenchmark.IntSet_Remove(ItemCount: 1000) 81.59 μs 8.81 μs 80.23 μs +1.7%
LongDictionaryBenchmark.LongDictionary_Remove(ItemCount: 1000) 87.83 μs 4.48 μs 84.97 μs +3.4%
LongSetBenchmark.LongSet_Remove(ItemCount: 1000) 80.49 μs 6.09 μs 77.53 μs +3.8%
CelerityDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.01 ms 30.94 μs 2.00 ms +0.8%
CelerityMultiMapBenchmark.Dictionary_Remove(ItemCount: 100000) 2.23 ms 141.39 μs 2.06 ms +8.4%
CeleritySetBenchmark.HashSet_Remove(ItemCount: 100000) 1.67 ms 21.56 μs 1.67 ms +0.1%
IntDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.01 ms 24.70 μs 1.98 ms +1.4%
IntSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.66 ms 15.68 μs 1.66 ms -0.2%
LongDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 1.97 ms 32.45 μs 1.97 ms -0.2%
LongSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.92 ms 21.70 μs 1.91 ms +0.5%
CelerityDictionaryBenchmark.CelerityDictionary_Remove(ItemCount: 100000) 1.89 ms 229.41 μs 1.73 ms +8.8%
CelerityMultiMapBenchmark.CelerityMultiMap_Remove(ItemCount: 100000) 2.34 ms 123.72 μs 2.00 ms +17.1% ⚠️
CeleritySetBenchmark.CeleritySet_Remove(ItemCount: 100000) 1.40 ms 59.75 μs 1.43 ms -2.0%
IntDictionaryBenchmark.IntDictionary_Remove(ItemCount: 100000) 2.51 ms 1.68 ms 6.31 ms -60.2% ✅
IntSetBenchmark.IntSet_Remove(ItemCount: 100000) 1.43 ms 13.83 μs 1.43 ms -0.1%
LongDictionaryBenchmark.LongDictionary_Remove(ItemCount: 100000) 1.81 ms 110.95 μs 1.85 ms -2.1%
LongSetBenchmark.LongSet_Remove(ItemCount: 100000) 1.56 ms 25.27 μs 1.56 ms +0.4%
Hashers (90)
Benchmark This PR StdDev main Δ
IntegerHasherBenchmark.Guid_Bcl 1.49 μs 3.2 ns 1.52 μs -1.7%
IntegerHasherBenchmark.Guid_Celerity 9.10 μs 7.2 ns 9.10 μs +0.0%
StringHasherBenchmark.Bcl_GetHashCode(Shape: ShortAscii) 16.65 μs 18.8 ns 16.67 μs -0.1%
StringHasherBenchmark.Djb2(Shape: ShortAscii) 23.85 μs 21.8 ns 23.86 μs -0.0%
StringHasherBenchmark.Djb2A(Shape: ShortAscii) 23.87 μs 36.9 ns 23.84 μs +0.1%
StringHasherBenchmark.Sdbm(Shape: ShortAscii) 34.66 μs 56.0 ns 34.65 μs +0.0%
StringHasherBenchmark.Elf(Shape: ShortAscii) 43.12 μs 26.9 ns 43.12 μs +0.0%
StringHasherBenchmark.Crc32(Shape: ShortAscii) 54.72 μs 46.8 ns 54.77 μs -0.1%
StringHasherBenchmark.Adler32(Shape: ShortAscii) 108.62 μs 70.6 ns 108.63 μs -0.0%
StringHasherBenchmark.FnV1(Shape: ShortAscii) 28.58 μs 31.6 ns 28.59 μs -0.0%
StringHasherBenchmark.FnV1_64(Shape: ShortAscii) 28.01 μs 27.4 ns 28.01 μs +0.0%
StringHasherBenchmark.FnV1A(Shape: ShortAscii) 12.70 μs 9.9 ns 12.72 μs -0.1%
StringHasherBenchmark.FnV1A_Full(Shape: ShortAscii) 24.64 μs 16.4 ns 24.62 μs +0.1%
StringHasherBenchmark.FnV1A_64(Shape: ShortAscii) 29.51 μs 14.3 ns 29.52 μs -0.0%
StringHasherBenchmark.JenkinsOaat(Shape: ShortAscii) 41.05 μs 19.6 ns 41.04 μs +0.0%
StringHasherBenchmark.Murmur2(Shape: ShortAscii) 15.92 μs 15.2 ns 15.91 μs +0.1%
StringHasherBenchmark.Murmur3(Shape: ShortAscii) 18.19 μs 9.3 ns 18.20 μs -0.1%
StringHasherBenchmark.XxHash32(Shape: ShortAscii) 17.43 μs 30.8 ns 17.42 μs +0.0%
StringHasherBenchmark.XxHash64(Shape: ShortAscii) 19.91 μs 21.5 ns 19.91 μs +0.0%
StringHasherBenchmark.XxHash3(Shape: ShortAscii) 18.10 μs 9.4 ns 18.13 μs -0.2%
StringHasherBenchmark.CityHash64(Shape: ShortAscii) 16.64 μs 8.0 ns 16.63 μs +0.0%
StringHasherBenchmark.MetroHash64(Shape: ShortAscii) 16.86 μs 15.1 ns 16.86 μs +0.0%
StringHasherBenchmark.SipHash13(Shape: ShortAscii) 30.72 μs 33.7 ns 30.72 μs +0.0%
StringHasherBenchmark.SipHash24(Shape: ShortAscii) 43.29 μs 70.7 ns 43.21 μs +0.2%
StringHasherBenchmark.HalfSipHash24(Shape: ShortAscii) 55.98 μs 16.1 ns 56.03 μs -0.1%
StringHasherBenchmark.HighwayHash64(Shape: ShortAscii) 447.50 μs 294.4 ns 447.73 μs -0.1%
StringHasherBenchmark.Bcl_GetHashCode(Shape: LongAscii) 108.16 μs 35.4 ns 108.16 μs -0.0%
StringHasherBenchmark.Djb2(Shape: LongAscii) 248.06 μs 174.9 ns 248.18 μs -0.0%
StringHasherBenchmark.Djb2A(Shape: LongAscii) 248.09 μs 208.9 ns 248.01 μs +0.0%
StringHasherBenchmark.Sdbm(Shape: LongAscii) 342.90 μs 149.3 ns 342.82 μs +0.0%
StringHasherBenchmark.Elf(Shape: LongAscii) 624.65 μs 397.3 ns 626.30 μs -0.3%
StringHasherBenchmark.Crc32(Shape: LongAscii) 661.26 μs 271.0 ns 661.35 μs -0.0%
StringHasherBenchmark.Adler32(Shape: LongAscii) 882.95 μs 974.1 ns 883.09 μs -0.0%
StringHasherBenchmark.FnV1(Shape: LongAscii) 321.86 μs 105.3 ns 321.81 μs +0.0%
StringHasherBenchmark.FnV1_64(Shape: LongAscii) 319.69 μs 65.1 ns 319.77 μs -0.0%
StringHasherBenchmark.FnV1A(Shape: LongAscii) 133.11 μs 108.6 ns 133.08 μs +0.0%
StringHasherBenchmark.FnV1A_Full(Shape: LongAscii) 313.08 μs 99.0 ns 313.11 μs -0.0%
StringHasherBenchmark.FnV1A_64(Shape: LongAscii) 319.41 μs 190.1 ns 319.39 μs +0.0%
StringHasherBenchmark.JenkinsOaat(Shape: LongAscii) 422.32 μs 127.6 ns 422.41 μs -0.0%
StringHasherBenchmark.Murmur2(Shape: LongAscii) 100.18 μs 2.72 μs 98.22 μs +2.0%
StringHasherBenchmark.Murmur3(Shape: LongAscii) 113.96 μs 155.0 ns 113.23 μs +0.6%
StringHasherBenchmark.XxHash32(Shape: LongAscii) 76.59 μs 29.5 ns 76.61 μs -0.0%
StringHasherBenchmark.XxHash64(Shape: LongAscii) 93.44 μs 131.6 ns 93.50 μs -0.1%
StringHasherBenchmark.XxHash3(Shape: LongAscii) 84.60 μs 67.6 ns 84.64 μs -0.1%
StringHasherBenchmark.CityHash64(Shape: LongAscii) 133.34 μs 85.7 ns 133.22 μs +0.1%
StringHasherBenchmark.MetroHash64(Shape: LongAscii) 82.34 μs 147.0 ns 82.35 μs -0.0%
StringHasherBenchmark.SipHash13(Shape: LongAscii) 115.53 μs 96.1 ns 115.57 μs -0.0%
StringHasherBenchmark.SipHash24(Shape: LongAscii) 169.64 μs 73.7 ns 169.75 μs -0.1%
StringHasherBenchmark.HalfSipHash24(Shape: LongAscii) 271.96 μs 200.2 ns 272.03 μs -0.0%
StringHasherBenchmark.HighwayHash64(Shape: LongAscii) 774.42 μs 868.1 ns 772.21 μs +0.3%
StringHasherBenchmark.Bcl_GetHashCode(Shape: NonAscii) 25.06 μs 13.3 ns 25.06 μs +0.0%
StringHasherBenchmark.Djb2(Shape: NonAscii) 43.24 μs 37.0 ns 43.26 μs -0.0%
StringHasherBenchmark.Djb2A(Shape: NonAscii) 43.24 μs 36.4 ns 43.24 μs -0.0%
StringHasherBenchmark.Sdbm(Shape: NonAscii) 65.08 μs 44.7 ns 65.09 μs -0.0%
StringHasherBenchmark.Elf(Shape: NonAscii) 87.46 μs 42.2 ns 87.47 μs -0.0%
StringHasherBenchmark.Crc32(Shape: NonAscii) 111.30 μs 54.3 ns 111.32 μs -0.0%
StringHasherBenchmark.Adler32(Shape: NonAscii) 193.43 μs 88.1 ns 193.36 μs +0.0%
StringHasherBenchmark.FnV1(Shape: NonAscii) 51.06 μs 23.1 ns 51.05 μs +0.0%
StringHasherBenchmark.FnV1_64(Shape: NonAscii) 51.17 μs 35.6 ns 51.15 μs +0.0%
StringHasherBenchmark.FnV1A(Shape: NonAscii) 23.65 μs 9.6 ns 23.65 μs +0.0%
StringHasherBenchmark.FnV1A_Full(Shape: NonAscii) 46.80 μs 23.9 ns 46.81 μs -0.0%
StringHasherBenchmark.FnV1A_64(Shape: NonAscii) 51.84 μs 34.8 ns 51.83 μs +0.0%
StringHasherBenchmark.JenkinsOaat(Shape: NonAscii) 77.55 μs 96.4 ns 77.54 μs +0.0%
StringHasherBenchmark.Murmur2(Shape: NonAscii) 24.55 μs 14.0 ns 24.56 μs -0.0%
StringHasherBenchmark.Murmur3(Shape: NonAscii) 27.68 μs 29.2 ns 27.68 μs -0.0%
StringHasherBenchmark.XxHash32(Shape: NonAscii) 24.57 μs 13.3 ns 24.59 μs -0.1%
StringHasherBenchmark.XxHash64(Shape: NonAscii) 32.34 μs 37.8 ns 32.35 μs -0.0%
StringHasherBenchmark.XxHash3(Shape: NonAscii) 28.88 μs 29.6 ns 28.86 μs +0.0%
StringHasherBenchmark.CityHash64(Shape: NonAscii) 23.77 μs 36.8 ns 23.75 μs +0.1%
StringHasherBenchmark.MetroHash64(Shape: NonAscii) 27.64 μs 119.2 ns 27.52 μs +0.4%
StringHasherBenchmark.SipHash13(Shape: NonAscii) 39.73 μs 34.7 ns 39.75 μs -0.0%
StringHasherBenchmark.SipHash24(Shape: NonAscii) 57.00 μs 53.6 ns 56.98 μs +0.0%
StringHasherBenchmark.HalfSipHash24(Shape: NonAscii) 79.25 μs 57.5 ns 79.26 μs -0.0%
StringHasherBenchmark.HighwayHash64(Shape: NonAscii) 481.54 μs 5.85 μs 478.46 μs +0.6%
IntegerHasherBenchmark.Int32_Bcl 773.7 ns 0.4 ns 774.7 ns -0.1%
IntegerHasherBenchmark.Int32_WangNaive 1.41 μs 0.3 ns 1.41 μs -0.3%
IntegerHasherBenchmark.Int32_Wang 3.42 μs 1.7 ns 3.42 μs -0.0%
IntegerHasherBenchmark.Int32_Murmur3 3.05 μs 1.2 ns 3.05 μs -0.1%
IntegerHasherBenchmark.Int64_Bcl 1.68 μs 0.4 ns 1.68 μs -0.0%
IntegerHasherBenchmark.Int64_WangNaive 2.10 μs 1.0 ns 2.10 μs -0.0%
IntegerHasherBenchmark.Int64_Wang 4.70 μs 3.0 ns 4.70 μs -0.0%
IntegerHasherBenchmark.Int64_Murmur3 2.68 μs 0.9 ns 2.68 μs -0.1%
IntegerHasherBenchmark.UInt32_Bcl 773.8 ns 0.5 ns 774.5 ns -0.1%
IntegerHasherBenchmark.UInt32_Default 1.41 μs 0.4 ns 1.41 μs -0.0%
IntegerHasherBenchmark.UInt32_Wang 3.42 μs 2.7 ns 3.42 μs +0.0%
IntegerHasherBenchmark.UInt32_Murmur3 3.05 μs 2.6 ns 3.05 μs +0.0%
IntegerHasherBenchmark.UInt64_Bcl 1.68 μs 0.3 ns 1.68 μs -0.0%
IntegerHasherBenchmark.UInt64_Default 2.68 μs 1.8 ns 2.68 μs -0.0%
IntegerHasherBenchmark.UInt64_Wang 4.70 μs 5.5 ns 4.70 μs -0.0%
IntegerHasherBenchmark.UInt64_WangNaive 2.10 μs 1.7 ns 2.10 μs -0.0%

Same-runner A/B: main (360c1d2) and this PR were built and benchmarked back-to-back on the same runner, so hardware variance cancels out. ⚠️ = PR mean ≥ +10% slower than main and beyond combined std-dev; ✅ = correspondingly faster.

@marius-bughiu
marius-bughiu deleted the fix/issue-27-bulk-ctor-resize branch July 24, 2026 22:45
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