Skip to content

feat(collections): add FenwickTree<T> — Binary Indexed Tree with O(log n) point update & prefix-sum query - #290

Merged
marius-bughiu merged 12 commits into
mainfrom
feat/issue-289-fenwick-tree
Jul 25, 2026
Merged

feat(collections): add FenwickTree<T> — Binary Indexed Tree with O(log n) point update & prefix-sum query#290
marius-bughiu merged 12 commits into
mainfrom
feat/issue-289-fenwick-tree

Conversation

@marius-bughiu

@marius-bughiu marius-bughiu commented Jul 22, 2026

Copy link
Copy Markdown
Owner

Summary

Adds FenwickTree<T> (Binary Indexed Tree) to Celerity.Collections — a fixed-length, array-backed numeric sequence that applies point updates and answers prefix / range sums both in O(log n), in one n-element array with no per-node overhead. Generic over System.Numerics.INumber<T> (int, long, uint, ulong, double, decimal, …).

Closes #289.

The BCL gap / documented winning workload

.NET ships nothing for the interleaved point-update + prefix-sum-query workload, and a plain T[] forces a losing tradeoff — keep the raw values and every prefix/range query is O(n) (point updates are O(1)); precompute a running-total array and queries are O(1) but every update is O(n). A Fenwick tree gives both in O(log n). It wins precisely when updates and partial-sum queries interleave: running aggregates, rank / order-statistics counters (inversions, "how many seen ≤ x"), cumulative-frequency tables, windowed sums over a mutating history.

Parity rollout (all in this PR)

  • Collection — src/Celerity/Collections/FenwickTree.cs: int and IEnumerable<T> (O(n) build) constructors; Count / Total, indexer get/set, Add, PrefixSum, RangeSum, Clear; allocation-free struct enumerator with version-based mutation detection; IReadOnlyCollection<T>.
  • Dedicated tests — FenwickTreeTests (core ops, indexer, constructors + validation + source-array non-aliasing, int/long/double, full enumeration surface) and FenwickTreeDifferentialTests (seeded randomized reconciliation of every value / prefix boundary / range query against a naive long[] oracle).
  • Benchmark — FenwickTreeBenchmark (Mixed interleaved update+query stream and RangeSum batch, vs a plain long[]), registered in Program.cs's CoreBenchmarks.
  • AOT smoke test — a FenwickTree block in Celerity.AotSmokeTest, matching every sibling collection. This is the only collection built on generic math, so its INumber<T> static abstract members resolve through constrained calls the AOT compiler must specialize per T; the block pins two instantiations (long, int) under Native AOT rather than JIT only.
  • Dashboard — ship card in web/index.html + COLLECTIONS arrays in web/dev/bench/index.html and web/dev/bench/detail.html.
  • Docs — full API section in docs/api/collections.md (with a runnable inversion-count example) + README (new "Prefix sums" group, a details block, and a decision-table row).
  • CHANGELOG — a single user-facing bullet under [Unreleased], matching the brevity convention main applies to the Trie / SparseSet entries.

Parity items that genuinely do not apply: the cross-collection shared Add / TryAdd / SetConstructorValidation / IEnumerableConstructor / EnsureCapacity suites (this is a numeric prefix-sum structure, not a hash-table set/dict — same as IndexedPriorityQueue / DisjointSet), and there is no ROADMAP.md status to flip (collection work is done through 2.1.0; this is the established post-roadmap tier-(c) pattern).

Review follow-ups (all addressed)

Seven Copilot rounds; every thread replied to and resolved.

Round 1 (747fd76)

  1. Length overflow — the constructor now rejects a length above Array.MaxLength - 1 (the 1-based layout reserves index 0) with a clear ArgumentOutOfRangeException instead of letting length + 1 overflow; the IEnumerable<T> ctor gets the matching guard.
  2. No-op version bumps — AddCore returns early on a zero delta, so neither Add(i, 0) nor assigning the value already stored bumps _version or invalidates active enumerators (and both skip the pointless O(log n) walk).
  3. Dashboard comment — corrected: for the raw-value long[] baseline, updates are O(1) and only the sums are O(n).
  4. CHANGELOG length — already condensed in 0de3100; the per-facet detail lives here in the PR body.

Round 2 (20dc83f) — a real dashboard bug. parseName() classifies a benchmark arm as the baseline only when BCL_TYPES contains its type name, and Array was missing, so both Array_* and FenwickTree_* landed in the celerity slot and the card would have rendered with no baseline or speedup. 'Array' added to BCL_TYPES in both index.html and detail.html.

Round 3 (1f77480, a06f467) — a real ordering bug. FenwickTree(IEnumerable<T>) called ToArray() before the length ceiling check, so an oversized ICollection<T> failed the allocation instead of reporting the documented ArgumentException. Counted sources are now length-checked first and copied straight into the 1-based backing array (no intermediate T[]); the throw helper also takes the parameter name.

Round 4 (0980e34, 4532ec8) — a real overflow bug. Both ascending walks (AddCore and the O(n) build) add the lowest set bit, so at k == 1 << 30 the next index is 1 << 31, which wraps negative in a signed int, still passes the <= _length guard, and then indexes out of bounds. Lengths that large are permitted (the ceiling is Array.MaxLength - 1), so this was reachable, not theoretical. Both cursors widened to long. The regression test builds a 2^30-cell FenwickTree<byte> (~1 GiB committed, ~17 ms — the runtime zeroes lazily) behind a new MemoryIntensiveFact attribute, which reports the test skipped on a memory-capped runner rather than failing the build.

Round 5 (8a0aab9) — extracted PrefixSumCore as the single unvalidated prefix walk, corrected the layout wording, hardened the new test attribute.

Round 6 (7f6fba1) — the enumerator called the public PrefixSum once per element even though MoveNext's own _index < _length guard already puts the bound in range, paying a redundant compare and keeping the throw path inside the loop body. It now calls PrefixSumCore directly. Applied the same reasoning to Total (whose check compared _length against itself), so PrefixSum is left as the single validating entry point for user-supplied bounds and every internal caller is on the core.

Round 7 — Copilot reviewed 7f6fba1 and generated no new comments. No threads left open.

0de3100 also merges main (Trie #286 / SparseSet #288 landed while this was open); every conflict was a keep-both resolution in the shared parity files.

CI: benchmark shard timeout

The benchmark gate came back cancelled on this branch — benchmark (shard 0) hit the 120 min job cap (65 min head slice, base slice cancelled 55 min in). The other five shards ran 86–106 min and passed.

Diagnosis, after measuring rather than assuming:

  • My first hypothesis was that this PR's Array_* baseline arms were pathologically expensive per case (97 ms / 81 ms at 100k) and that the shard balancer, which weights by case count, could not see it. That was wrong. Cutting the per-op work 10× (arms down to 9.7 ms / 7.8 ms) left the class's wall-clock unchanged — 5m27s vs 5m38s — because BenchmarkDotNet scales invocations per iteration to hit a fixed iteration duration. Wall time tracks case count, not per-case cost, so the balancer's metric is correct and that change bought nothing. I reverted it.
  • The real cause is capacity. At SHARD_TOTAL=6 the heaviest shard had no headroom: even removing this PR's 8 cases entirely leaves it at ~119 min against a 120 min cap. main stays green only because push runs skip the base half, which hid how close the PR path had drifted.

Fix: raise SHARD_TOTAL 6 → 8 (matrix 0..7). CiConfig.cs is explicit that the job schedule stays as-is and the matrix is what scales when the suite grows, so this preserves measurement accuracy. The same work over 8 slices puts the heaviest back near ~95 min. The aggregate job already globs head-shard-*.json / base-shard-*.json and depends on the whole matrix, so it needs no change.

Also replaced the TRANSITIONAL comment on the base step: it predicted base runs would be cancelled until --shard landed on main and would then self-heal, but --shard is on main and the base tip honours it. The note was stale and would invite a reader to dismiss exactly this cancellation as expected.

Test plan

  • dotnet build clean across net8.0;net9.0;net10.0 (0 warnings — XML-doc gate passes).
  • dotnet test — full Celerity.Tests suite green (4420 passed, 0 failed, on both the net8.0 floor and net10.0), including the differential oracle and the new regression tests for the two behaviour fixes above.
  • Celerity.AotSmokeTest runs clean locally ("all checks passed").
  • CI matrix (Windows / Linux / macOS × net8/9/10), the aot-publish Native AOT matrix, and the coverage gate (line ≥ 95%, branch ≥ 90%).
  • Benchmark joins the per-PR core run; on merge to main the gh-pages dashboard refreshes with the new FenwickTree card.

🤖 Generated with Claude Code

…g n) point update & prefix-sum query

FenwickTree<T> (where T : struct, INumber<T>) maintains a fixed-length numeric
sequence and answers prefix / range sums and applies point updates both in
O(log n), in one n-element array with no per-node overhead. It fills a BCL gap:
there is no prefix-sum structure, and a plain array is O(n) per query or O(n) per
update. The documented BCL-beating workload is any stream that interleaves
updates with range-sum queries (running aggregates, rank / order-statistics
counters, cumulative-frequency tables).

Full parity rollout in one PR:
- collection: src/Celerity/Collections/FenwickTree.cs
- tests: FenwickTreeTests + FenwickTreeDifferentialTests (vs a naive long[] oracle)
- benchmark: FenwickTreeBenchmark (Mixed + RangeSum vs long[]), registered in Program.cs CoreBenchmarks
- dashboard: web/index.html ship card + both bench COLLECTIONS arrays
- docs: docs/api/collections.md section + README (list, details block, decision table)
- CHANGELOG: [Unreleased] bullet per facet

The hash-table shared-test suites do not apply (numeric prefix-sum structure, like
IndexedPriorityQueue / DisjointSet). Roadmap collection work is done through 2.1.0;
this is the established post-roadmap tier-(c) enhancement pattern.

Closes #289.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 01:18
@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Coverage

Metric Value
Line 98.55% (7011/7114)
Branch 97.06% (2943/3032)
Files below 100% line coverage
File Line Branch
src/Celerity/Collections/TopKEntry.cs 80% n/a
src/Celerity/Collections/XorFilter.cs 94.29% 90%
src/Celerity/Collections/EnumMap.cs 95.09% 92.19%
src/Celerity/Collections/FenwickTree.cs 96.12% 94.74%
src/Celerity/Collections/IndexedPriorityQueue.cs 96.23% 90.91%

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown

Benchmarks

2 regressions ⚠️ vs main, 11 improvements ✅ (rows past ±10% beyond noise).

Highlights

Benchmark This PR StdDev main Δ
SmallSetBenchmark.SmallSet_Add(ItemCount: 64) 1.67 μs 17.9 ns 2.00 μs -16.4% ✅
SwissSetBenchmark.HashSet_Add(ItemCount: 1000) 12.85 μs 90.8 ns 14.25 μs -9.8% ✅
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Build(ItemCount: 1000) 4.14 ms 29.94 μs 6.09 ms -32.1% ✅
CelerityDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 13.43 μs 90.9 ns 15.25 μs -11.9% ✅
CelerityMultiMapBenchmark.Dictionary_Remove(ItemCount: 100000) 2.28 ms 182.84 μs 3.58 ms -36.2% ✅
CelerityDictionaryBenchmark.CelerityDictionary_Remove(ItemCount: 100000) 1.57 ms 37.67 μs 1.96 ms -19.7% ✅
CelerityMultiMapBenchmark.CelerityMultiMap_Remove(ItemCount: 100000) 1.99 ms 102.49 μs 2.94 ms -32.4% ✅
SwissSetBenchmark.SwissSet_Remove(ItemCount: 100000) 1.09 ms 15.04 μs 1.38 ms -20.8% ✅
RobinHoodDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.04 ms 22.48 μs 2.36 ms -13.6% ✅
EnumSetBenchmark.HashSet_Remove 3.04 μs 457.9 ns 2.35 μs +29.6% ⚠️
CuckooFilterBenchmark.HashSet_Add(ItemCount: 1000) 12.24 μs 113.2 ns 13.74 μs -10.9% ✅
DequeBenchmark.LinkedList_PushFront(ItemCount: 1000) 44.71 μs 5.97 μs 35.36 μs +26.5% ⚠️
DisjointSetBenchmark.Dictionary_Union(ItemCount: 100000) 45.37 ms 1.85 ms 52.11 ms -12.9% ✅
Collections (420)
Benchmark This PR StdDev main Δ
TrieBenchmark.Dictionary_Add(ItemCount: 1000) 34.06 μs 2.11 μs 32.60 μs +4.5%
TrieBenchmark.Trie_Add(ItemCount: 1000) 397.00 μs 16.26 μs 390.02 μs +1.8%
LongSetBenchmark.HashSet_Add(ItemCount: 1000) 10.71 μs 87.6 ns 10.79 μs -0.7%
LongSetBenchmark.LongSet_Add(ItemCount: 1000) 7.29 μs 303.3 ns 7.81 μs -6.7%
TrieBenchmark.Dictionary_Add(ItemCount: 100000) 4.66 ms 1.46 ms 4.61 ms +1.1%
TrieBenchmark.Trie_Add(ItemCount: 100000) 22.51 ms 910.47 μs 21.74 ms +3.5%
LongSetBenchmark.HashSet_Add(ItemCount: 100000) 4.18 ms 110.94 μs 4.18 ms +0.1%
LongSetBenchmark.LongSet_Add(ItemCount: 100000) 5.38 ms 37.17 μs 5.32 ms +1.1%
LongSetBenchmark.HashSet_Contains(ItemCount: 1000) 3.96 μs 6.9 ns 3.96 μs -0.1%
LongSetBenchmark.LongSet_Contains(ItemCount: 1000) 1.62 μs 4.2 ns 1.62 μs +0.2%
LongSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.25 ms 15.02 μs 1.23 ms +2.0%
LongSetBenchmark.LongSet_Contains(ItemCount: 100000) 453.32 μs 12.68 μs 443.50 μs +2.2%
LongDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 12.34 μs 185.4 ns 12.03 μs +2.6%
LongDictionaryBenchmark.LongDictionary_Insert(ItemCount: 1000) 6.85 μs 110.4 ns 6.96 μs -1.6%
LongDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 4.21 ms 66.90 μs 4.20 ms +0.3%
LongDictionaryBenchmark.LongDictionary_Insert(ItemCount: 100000) 6.19 ms 93.49 μs 6.12 ms +1.2%
LongDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 3.87 μs 7.7 ns 3.93 μs -1.3%
TrieBenchmark.Dictionary_Lookup(ItemCount: 1000) 9.77 μs 26.0 ns 9.75 μs +0.1%
LongDictionaryBenchmark.LongDictionary_Lookup(ItemCount: 1000) 1.76 μs 7.0 ns 1.76 μs +0.0%
TrieBenchmark.Trie_Lookup(ItemCount: 1000) 35.32 μs 74.3 ns 35.33 μs -0.0%
LongDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.24 ms 16.12 μs 1.23 ms +1.0%
TrieBenchmark.Dictionary_Lookup(ItemCount: 100000) 2.17 ms 5.54 μs 2.16 ms +0.5%
LongDictionaryBenchmark.LongDictionary_Lookup(ItemCount: 100000) 549.53 μs 5.73 μs 555.73 μs -1.1%
TrieBenchmark.Trie_Lookup(ItemCount: 100000) 7.98 ms 55.31 μs 8.01 ms -0.3%
TrieBenchmark.Dictionary_PrefixMatch(ItemCount: 1000) 62.07 μs 134.2 ns 62.25 μs -0.3%
TrieBenchmark.Trie_PrefixMatch(ItemCount: 1000) 42.89 μs 753.8 ns 43.39 μs -1.1%
TrieBenchmark.Dictionary_PrefixMatch(ItemCount: 100000) 6.29 ms 8.37 μs 6.27 ms +0.4%
TrieBenchmark.Trie_PrefixMatch(ItemCount: 100000) 6.53 ms 100.93 μs 6.55 ms -0.4%
LongDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 62.36 μs 5.50 μs 61.99 μs +0.6%
LongSetBenchmark.HashSet_Remove(ItemCount: 1000) 55.63 μs 3.79 μs 55.49 μs +0.3%
LongDictionaryBenchmark.LongDictionary_Remove(ItemCount: 1000) 64.89 μs 4.64 μs 63.27 μs +2.6%
LongSetBenchmark.LongSet_Remove(ItemCount: 1000) 64.22 μs 5.80 μs 62.19 μs +3.2%
LongDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 1.55 ms 49.18 μs 1.56 ms -0.3%
LongSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.51 ms 18.58 μs 1.50 ms +0.5%
LongDictionaryBenchmark.LongDictionary_Remove(ItemCount: 100000) 1.49 ms 129.65 μs 1.50 ms -0.6%
LongSetBenchmark.LongSet_Remove(ItemCount: 100000) 1.23 ms 45.71 μs 1.25 ms -1.4%
SmallSetBenchmark.HashSet_Add(ItemCount: 8) 180.8 ns 18.1 ns 183.5 ns -1.4%
SmallSetBenchmark.SmallSet_Add(ItemCount: 8) 45.9 ns 0.4 ns 49.7 ns -7.7%
SmallSetBenchmark.HashSet_Add(ItemCount: 64) 746.5 ns 7.6 ns 813.7 ns -8.3%
SmallSetBenchmark.SmallSet_Add(ItemCount: 64) 1.67 μs 17.9 ns 2.00 μs -16.4% ✅
SwissSetBenchmark.HashSet_Add(ItemCount: 1000) 12.85 μs 90.8 ns 14.25 μs -9.8% ✅
TopKSketchBenchmark.Dictionary_Add(ItemCount: 1000) 14.05 μs 88.5 ns 14.14 μs -0.7%
SwissSetBenchmark.SwissSet_Add(ItemCount: 1000) 26.53 μs 48.8 ns 27.19 μs -2.5%
TopKSketchBenchmark.TopKSketch_Add(ItemCount: 1000) 82.56 μs 491.5 ns 82.63 μs -0.1%
SwissSetBenchmark.HashSet_Add(ItemCount: 100000) 4.79 ms 90.65 μs 4.95 ms -3.2%
TopKSketchBenchmark.Dictionary_Add(ItemCount: 100000) 3.35 ms 91.94 μs 3.36 ms -0.3%
SwissSetBenchmark.SwissSet_Add(ItemCount: 100000) 4.64 ms 83.50 μs 4.65 ms -0.2%
TopKSketchBenchmark.TopKSketch_Add(ItemCount: 100000) 13.68 ms 37.91 μs 13.79 ms -0.8%
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Build(ItemCount: 1000) 160.70 μs 569.4 ns 164.61 μs -2.4%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Build(ItemCount: 1000) 4.14 ms 29.94 μs 6.09 ms -32.1% ✅
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Build(ItemCount: 100000) 29.68 ms 406.12 μs 31.37 ms -5.4%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Build(ItemCount: 100000) 2.10 s 202.77 ms 2.17 s -3.4%
SmallSetBenchmark.HashSet_Contains(ItemCount: 8) 37.1 ns 0.0 ns 37.2 ns -0.2%
SmallSetBenchmark.SmallSet_Contains(ItemCount: 8) 19.1 ns 0.1 ns 19.3 ns -0.8%
SmallSetBenchmark.HashSet_Contains(ItemCount: 64) 297.6 ns 1.5 ns 299.4 ns -0.6%
SmallSetBenchmark.SmallSet_Contains(ItemCount: 64) 1.12 μs 18.7 ns 1.12 μs -0.3%
SwissSetBenchmark.HashSet_Contains(ItemCount: 1000) 4.75 μs 44.5 ns 4.95 μs -4.0%
SwissSetBenchmark.SwissSet_Contains(ItemCount: 1000) 2.77 μs 2.9 ns 2.78 μs -0.3%
SwissSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.60 ms 3.21 μs 1.56 ms +2.3%
SwissSetBenchmark.SwissSet_Contains(ItemCount: 100000) 654.07 μs 1.75 μs 647.79 μs +1.0%
SwissSetBenchmark.HashSet_ContainsMissing(ItemCount: 1000) 4.56 μs 21.1 ns 4.54 μs +0.4%
SwissSetBenchmark.SwissSet_ContainsMissing(ItemCount: 1000) 2.37 μs 5.4 ns 2.37 μs -0.0%
SwissSetBenchmark.HashSet_ContainsMissing(ItemCount: 100000) 1.95 ms 3.25 μs 1.93 ms +1.5%
SwissSetBenchmark.SwissSet_ContainsMissing(ItemCount: 100000) 347.40 μs 4.49 μs 345.05 μs +0.7%
CelerityDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 13.43 μs 90.9 ns 15.25 μs -11.9% ✅
CelerityMultiMapBenchmark.Dictionary_Insert(ItemCount: 1000) 29.60 μs 414.2 ns 31.43 μs -5.8%
CelerityDictionaryBenchmark.CelerityDictionary_Insert(ItemCount: 1000) 11.78 μs 69.2 ns 12.73 μs -7.4%
CelerityMultiMapBenchmark.CelerityMultiMap_Insert(ItemCount: 1000) 37.97 μs 283.6 ns 40.31 μs -5.8%
CelerityDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 4.20 ms 58.65 μs 4.35 ms -3.3%
CelerityMultiMapBenchmark.Dictionary_Insert(ItemCount: 100000) 12.71 ms 589.80 μs 13.69 ms -7.2%
CelerityDictionaryBenchmark.CelerityDictionary_Insert(ItemCount: 100000) 4.82 ms 44.74 μs 5.02 ms -4.1%
CelerityMultiMapBenchmark.CelerityMultiMap_Insert(ItemCount: 100000) 17.42 ms 123.22 μs 18.84 ms -7.5%
CelerityDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.73 μs 14.3 ns 4.70 μs +0.7%
CelerityMultiMapBenchmark.Dictionary_Lookup(ItemCount: 1000) 5.06 μs 12.1 ns 5.00 μs +1.3%
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Lookup(ItemCount: 1000) 7.33 μs 9.0 ns 7.40 μs -1.0%
CelerityDictionaryBenchmark.CelerityDictionary_Lookup(ItemCount: 1000) 2.16 μs 10.5 ns 2.15 μs +0.4%
CelerityMultiMapBenchmark.CelerityMultiMap_Lookup(ItemCount: 1000) 2.27 μs 6.3 ns 2.27 μs +0.0%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Lookup(ItemCount: 1000) 90.16 μs 4.31 μs 88.11 μs +2.3%
CelerityDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.59 ms 12.44 μs 1.60 ms -0.5%
CelerityMultiMapBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.74 ms 2.33 μs 1.78 ms -1.9%
FrozenCelerityDictionaryBenchmark.FrozenDictionary_Lookup(ItemCount: 100000) 2.11 ms 12.01 μs 2.10 ms +0.9%
CelerityDictionaryBenchmark.CelerityDictionary_Lookup(ItemCount: 100000) 721.57 μs 24.38 μs 691.11 μs +4.4%
CelerityMultiMapBenchmark.CelerityMultiMap_Lookup(ItemCount: 100000) 713.96 μs 1.95 μs 763.45 μs -6.5%
FrozenCelerityDictionaryBenchmark.FrozenCelerityDictionary_Lookup(ItemCount: 100000) 8.52 ms 420.40 μs 8.71 ms -2.2%
SmallSetBenchmark.HashSet_Remove(ItemCount: 8) 453.2 ns 150.2 ns 415.9 ns +9.0%
SmallSetBenchmark.SmallSet_Remove(ItemCount: 8) 1.35 μs 257.0 ns 1.50 μs -9.9%
SmallSetBenchmark.HashSet_Remove(ItemCount: 64) 1.66 μs 356.1 ns 1.33 μs +24.8%
SmallSetBenchmark.SmallSet_Remove(ItemCount: 64) 24.25 μs 4.68 μs 27.35 μs -11.3%
CelerityDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 88.46 μs 9.62 μs 86.40 μs +2.4%
CelerityMultiMapBenchmark.Dictionary_Remove(ItemCount: 1000) 30.26 μs 4.72 μs 32.78 μs -7.7%
CelerityDictionaryBenchmark.CelerityDictionary_Remove(ItemCount: 1000) 141.37 μs 7.69 μs 144.33 μs -2.1%
CelerityMultiMapBenchmark.CelerityMultiMap_Remove(ItemCount: 1000) 149.34 μs 7.83 μs 149.30 μs +0.0%
SwissSetBenchmark.HashSet_Remove(ItemCount: 1000) 31.92 μs 4.81 μs 34.24 μs -6.8%
SwissSetBenchmark.SwissSet_Remove(ItemCount: 1000) 67.56 μs 4.78 μs 71.03 μs -4.9%
CelerityDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.07 ms 20.40 μs 2.25 ms -8.4%
CelerityMultiMapBenchmark.Dictionary_Remove(ItemCount: 100000) 2.28 ms 182.84 μs 3.58 ms -36.2% ✅
CelerityDictionaryBenchmark.CelerityDictionary_Remove(ItemCount: 100000) 1.57 ms 37.67 μs 1.96 ms -19.7% ✅
CelerityMultiMapBenchmark.CelerityMultiMap_Remove(ItemCount: 100000) 1.99 ms 102.49 μs 2.94 ms -32.4% ✅
SwissSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.73 ms 22.89 μs 1.82 ms -5.0%
SwissSetBenchmark.SwissSet_Remove(ItemCount: 100000) 1.09 ms 15.04 μs 1.38 ms -20.8% ✅
TopKSketchBenchmark.Dictionary_TopK(ItemCount: 1000) 32.7 ns 0.3 ns 33.0 ns -0.8%
TopKSketchBenchmark.TopKSketch_TopK(ItemCount: 1000) 1.33 μs 12.6 ns 1.30 μs +2.0%
TopKSketchBenchmark.Dictionary_TopK(ItemCount: 100000) 33.2 ns 0.4 ns 33.6 ns -1.4%
TopKSketchBenchmark.TopKSketch_TopK(ItemCount: 100000) 1.29 μs 19.7 ns 1.24 μs +3.4%
EnumMapBenchmark.Dictionary_Add 595.0 ns 5.6 ns 598.1 ns -0.5%
EnumMapBenchmark.EnumMap_Add 149.2 ns 0.6 ns 149.4 ns -0.1%
BloomFilterBenchmark.HashSet_Add(ItemCount: 1000) 12.31 μs 308.7 ns 12.13 μs +1.4%
RobinHoodSetBenchmark.HashSet_Add(ItemCount: 1000) 12.04 μs 151.9 ns 12.25 μs -1.8%
BloomFilterBenchmark.BloomFilter_Add(ItemCount: 1000) 14.47 μs 11.3 ns 14.47 μs -0.0%
RobinHoodSetBenchmark.RobinHoodSet_Add(ItemCount: 1000) 16.91 μs 137.5 ns 17.12 μs -1.2%
BloomFilterBenchmark.HashSet_Add(ItemCount: 100000) 4.61 ms 116.54 μs 4.76 ms -3.1%
RobinHoodSetBenchmark.HashSet_Add(ItemCount: 100000) 4.57 ms 395.53 μs 4.58 ms -0.3%
BloomFilterBenchmark.BloomFilter_Add(ItemCount: 100000) 1.10 ms 1.07 μs 1.09 ms +0.5%
RobinHoodSetBenchmark.RobinHoodSet_Add(ItemCount: 100000) 6.21 ms 63.08 μs 6.18 ms +0.5%
BloomFilterBenchmark.HashSet_Contains(ItemCount: 1000) 4.74 μs 36.7 ns 4.73 μs +0.1%
RobinHoodSetBenchmark.HashSet_Contains(ItemCount: 1000) 4.72 μs 5.9 ns 4.87 μs -3.1%
BloomFilterBenchmark.BloomFilter_Contains(ItemCount: 1000) 13.12 μs 12.2 ns 13.11 μs +0.1%
RobinHoodSetBenchmark.RobinHoodSet_Contains(ItemCount: 1000) 2.44 μs 16.9 ns 2.46 μs -0.5%
BloomFilterBenchmark.HashSet_Contains(ItemCount: 100000) 1.56 ms 7.45 μs 1.56 ms -0.4%
RobinHoodSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.55 ms 72.61 μs 1.55 ms -0.4%
BloomFilterBenchmark.BloomFilter_Contains(ItemCount: 100000) 880.43 μs 11.35 μs 868.94 μs +1.3%
RobinHoodSetBenchmark.RobinHoodSet_Contains(ItemCount: 100000) 736.53 μs 2.82 μs 735.78 μs +0.1%
BloomFilterBenchmark.HashSet_ContainsMissing(ItemCount: 1000) 4.54 μs 3.8 ns 4.54 μs -0.0%
RobinHoodSetBenchmark.HashSet_ContainsMissing(ItemCount: 1000) 4.55 μs 7.3 ns 4.54 μs +0.1%
BloomFilterBenchmark.BloomFilter_ContainsMissing(ItemCount: 1000) 3.53 μs 10.6 ns 3.54 μs -0.2%
RobinHoodSetBenchmark.RobinHoodSet_ContainsMissing(ItemCount: 1000) 2.71 μs 3.0 ns 2.71 μs +0.1%
BloomFilterBenchmark.HashSet_ContainsMissing(ItemCount: 100000) 1.93 ms 15.54 μs 1.88 ms +3.0%
RobinHoodSetBenchmark.HashSet_ContainsMissing(ItemCount: 100000) 1.93 ms 6.01 μs 1.93 ms -0.4%
BloomFilterBenchmark.BloomFilter_ContainsMissing(ItemCount: 100000) 1.49 ms 484.5 ns 1.49 ms -0.0%
RobinHoodSetBenchmark.RobinHoodSet_ContainsMissing(ItemCount: 100000) 1.18 ms 3.83 μs 1.18 ms -0.1%
CelerityMultiSetBenchmark.Dictionary_Count(ItemCount: 1000) 10.03 μs 60.4 ns 10.06 μs -0.3%
CelerityMultiSetBenchmark.CelerityMultiSet_Count(ItemCount: 1000) 5.64 μs 34.6 ns 5.49 μs +2.8%
CelerityMultiSetBenchmark.Dictionary_Count(ItemCount: 100000) 1.44 ms 11.84 μs 1.46 ms -1.2%
CelerityMultiSetBenchmark.CelerityMultiSet_Count(ItemCount: 100000) 723.77 μs 14.40 μs 694.74 μs +4.2%
IndexedPriorityQueueBenchmark.PriorityQueue_DecreaseKey(ItemCount: 1000) 146.06 μs 1.47 μs 145.79 μs +0.2%
IndexedPriorityQueueBenchmark.IndexedPriorityQueue_DecreaseKey(ItemCount: 1000) 253.20 μs 720.8 ns 252.22 μs +0.4%
IndexedPriorityQueueBenchmark.PriorityQueue_DecreaseKey(ItemCount: 100000) 32.43 ms 119.12 μs 32.37 ms +0.2%
IndexedPriorityQueueBenchmark.IndexedPriorityQueue_DecreaseKey(ItemCount: 100000) 53.86 ms 202.36 μs 54.24 ms -0.7%
IndexedPriorityQueueBenchmark.PriorityQueue_Enqueue(ItemCount: 1000) 4.63 μs 92.8 ns 4.70 μs -1.4%
IndexedPriorityQueueBenchmark.IndexedPriorityQueue_Enqueue(ItemCount: 1000) 29.45 μs 169.2 ns 29.71 μs -0.9%
IndexedPriorityQueueBenchmark.PriorityQueue_Enqueue(ItemCount: 100000) 1.08 ms 19.57 μs 1.09 ms -0.9%
IndexedPriorityQueueBenchmark.IndexedPriorityQueue_Enqueue(ItemCount: 100000) 6.10 ms 201.00 μs 6.16 ms -0.9%
EnumMapBenchmark.Dictionary_Enumerate 50.2 ns 0.1 ns 50.6 ns -0.7%
EnumMapBenchmark.EnumMap_Enumerate 39.6 ns 0.1 ns 39.7 ns -0.3%
RobinHoodDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 13.04 μs 166.7 ns 13.17 μs -1.0%
RobinHoodDictionaryBenchmark.RobinHoodDictionary_Insert(ItemCount: 1000) 16.79 μs 464.8 ns 17.20 μs -2.4%
RobinHoodDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 4.70 ms 39.09 μs 4.77 ms -1.5%
RobinHoodDictionaryBenchmark.RobinHoodDictionary_Insert(ItemCount: 100000) 7.83 ms 480.54 μs 7.85 ms -0.3%
EnumMapBenchmark.Dictionary_Lookup 117.7 ns 0.1 ns 117.6 ns +0.1%
EnumMapBenchmark.EnumMap_Lookup 63.3 ns 0.2 ns 63.0 ns +0.5%
CelerityMultiSetBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.68 μs 4.8 ns 4.68 μs +0.0%
RobinHoodDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.72 μs 18.2 ns 4.75 μs -0.6%
CelerityMultiSetBenchmark.CelerityMultiSet_Lookup(ItemCount: 1000) 1.61 μs 4.7 ns 1.61 μs -0.3%
RobinHoodDictionaryBenchmark.RobinHoodDictionary_Lookup(ItemCount: 1000) 2.62 μs 3.3 ns 2.64 μs -0.8%
CelerityMultiSetBenchmark.Dictionary_Lookup(ItemCount: 100000) 655.44 μs 16.79 μs 630.20 μs +4.0%
RobinHoodDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.61 ms 1.14 μs 1.60 ms +0.8%
CelerityMultiSetBenchmark.CelerityMultiSet_Lookup(ItemCount: 100000) 191.49 μs 360.9 ns 190.43 μs +0.6%
RobinHoodDictionaryBenchmark.RobinHoodDictionary_Lookup(ItemCount: 100000) 825.18 μs 18.73 μs 814.85 μs +1.3%
EnumMapBenchmark.Dictionary_Remove 3.60 μs 477.0 ns 3.69 μs -2.4%
EnumMapBenchmark.EnumMap_Remove 1.63 μs 304.5 ns 1.69 μs -3.8%
CelerityMultiSetBenchmark.Dictionary_Remove(ItemCount: 1000) 55.14 μs 5.69 μs 58.08 μs -5.1%
RobinHoodDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 83.65 μs 7.77 μs 88.64 μs -5.6%
CelerityMultiSetBenchmark.CelerityMultiSet_Remove(ItemCount: 1000) 98.64 μs 5.44 μs 93.41 μs +5.6%
RobinHoodDictionaryBenchmark.RobinHoodDictionary_Remove(ItemCount: 1000) 134.08 μs 9.47 μs 139.18 μs -3.7%
RobinHoodSetBenchmark.HashSet_Remove(ItemCount: 1000) 26.44 μs 1.49 μs 26.43 μs +0.0%
RobinHoodSetBenchmark.RobinHoodSet_Remove(ItemCount: 1000) 135.68 μs 6.26 μs 134.31 μs +1.0%
CelerityMultiSetBenchmark.Dictionary_Remove(ItemCount: 100000) 540.29 μs 10.03 μs 564.85 μs -4.3%
RobinHoodDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.04 ms 22.48 μs 2.36 ms -13.6% ✅
CelerityMultiSetBenchmark.CelerityMultiSet_Remove(ItemCount: 100000) 956.09 μs 344.12 μs 873.10 μs +9.5%
RobinHoodDictionaryBenchmark.RobinHoodDictionary_Remove(ItemCount: 100000) 1.68 ms 30.44 μs 1.66 ms +0.8%
RobinHoodSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.70 ms 13.62 μs 1.71 ms -0.7%
RobinHoodSetBenchmark.RobinHoodSet_Remove(ItemCount: 100000) 1.36 ms 41.76 μs 1.38 ms -1.1%
HashCachingSetBenchmark.HashSet_Add(ItemCount: 1000) 13.51 μs 69.1 ns 13.74 μs -1.7%
HashCachingSetBenchmark.HashCachingSet_Add(ItemCount: 1000) 11.93 μs 361.9 ns 11.93 μs +0.0%
HashCachingSetBenchmark.HashSet_Add(ItemCount: 100000) 4.53 ms 526.79 μs 4.66 ms -2.9%
HashCachingSetBenchmark.HashCachingSet_Add(ItemCount: 100000) 5.43 ms 53.16 μs 5.35 ms +1.5%
FrozenCeleritySetBenchmark.FrozenSet_Build(ItemCount: 1000) 168.47 μs 967.6 ns 169.30 μs -0.5%
XorFilterBenchmark.HashSet_Build(ItemCount: 1000) 8.39 μs 50.6 ns 8.49 μs -1.2%
FrozenCeleritySetBenchmark.FrozenCeleritySet_Build(ItemCount: 1000) 3.54 ms 60.76 μs 3.85 ms -8.2%
XorFilterBenchmark.XorFilter_Build(ItemCount: 1000) 41.51 μs 690.9 ns 42.05 μs -1.3%
FrozenCeleritySetBenchmark.FrozenSet_Build(ItemCount: 100000) 30.89 ms 558.88 μs 30.83 ms +0.2%
XorFilterBenchmark.HashSet_Build(ItemCount: 100000) 2.18 ms 93.64 μs 2.23 ms -2.2%
FrozenCeleritySetBenchmark.FrozenCeleritySet_Build(ItemCount: 100000) 1.65 s 64.78 ms 1.77 s -7.1%
XorFilterBenchmark.XorFilter_Build(ItemCount: 100000) 13.19 ms 200.46 μs 13.35 ms -1.2%
FrozenCeleritySetBenchmark.FrozenSet_Contains(ItemCount: 1000) 6.87 μs 16.1 ns 6.87 μs -0.0%
HashCachingSetBenchmark.HashSet_Contains(ItemCount: 1000) 4.72 μs 6.6 ns 4.72 μs +0.0%
XorFilterBenchmark.HashSet_Contains(ItemCount: 1000) 4.75 μs 23.2 ns 4.73 μs +0.3%
FrozenCeleritySetBenchmark.FrozenCeleritySet_Contains(ItemCount: 1000) 89.81 μs 1.05 μs 89.91 μs -0.1%
HashCachingSetBenchmark.HashCachingSet_Contains(ItemCount: 1000) 2.42 μs 5.0 ns 2.42 μs -0.0%
XorFilterBenchmark.XorFilter_Contains(ItemCount: 1000) 6.76 μs 6.5 ns 6.77 μs -0.2%
FrozenCeleritySetBenchmark.FrozenSet_Contains(ItemCount: 100000) 2.05 ms 19.84 μs 2.10 ms -2.5%
HashCachingSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.59 ms 17.05 μs 1.60 ms -0.4%
XorFilterBenchmark.HashSet_Contains(ItemCount: 100000) 1.60 ms 16.63 μs 1.59 ms +0.7%
FrozenCeleritySetBenchmark.FrozenCeleritySet_Contains(ItemCount: 100000) 7.72 ms 187.74 μs 8.39 ms -8.0%
HashCachingSetBenchmark.HashCachingSet_Contains(ItemCount: 100000) 754.67 μs 2.88 μs 757.66 μs -0.4%
XorFilterBenchmark.XorFilter_Contains(ItemCount: 100000) 704.63 μs 473.0 ns 704.78 μs -0.0%
HashCachingSetBenchmark.HashSet_ContainsMissing(ItemCount: 1000) 4.55 μs 7.0 ns 4.57 μs -0.6%
XorFilterBenchmark.HashSet_ContainsMissing(ItemCount: 1000) 4.54 μs 5.8 ns 4.54 μs +0.1%
HashCachingSetBenchmark.HashCachingSet_ContainsMissing(ItemCount: 1000) 2.80 μs 5.6 ns 2.81 μs -0.2%
XorFilterBenchmark.XorFilter_ContainsMissing(ItemCount: 1000) 6.78 μs 12.7 ns 6.77 μs +0.2%
HashCachingSetBenchmark.HashSet_ContainsMissing(ItemCount: 100000) 1.97 ms 3.57 μs 1.95 ms +1.3%
XorFilterBenchmark.HashSet_ContainsMissing(ItemCount: 100000) 1.97 ms 4.18 μs 1.95 ms +0.8%
HashCachingSetBenchmark.HashCachingSet_ContainsMissing(ItemCount: 100000) 1.17 ms 1.44 μs 1.18 ms -0.2%
XorFilterBenchmark.XorFilter_ContainsMissing(ItemCount: 100000) 706.19 μs 835.9 ns 706.27 μs -0.0%
SmallDictionaryBenchmark.Dictionary_Insert(ItemCount: 8) 180.0 ns 1.2 ns 184.8 ns -2.6%
SmallDictionaryBenchmark.SmallDictionary_Insert(ItemCount: 8) 93.8 ns 1.9 ns 91.0 ns +3.1%
SmallDictionaryBenchmark.Dictionary_Insert(ItemCount: 64) 857.3 ns 12.7 ns 855.4 ns +0.2%
SmallDictionaryBenchmark.SmallDictionary_Insert(ItemCount: 64) 1.11 μs 7.0 ns 1.13 μs -1.4%
SwissDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 14.54 μs 85.4 ns 14.87 μs -2.2%
SwissDictionaryBenchmark.SwissDictionary_Insert(ItemCount: 1000) 31.25 μs 103.0 ns 31.31 μs -0.2%
SwissDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 4.87 ms 151.45 μs 4.96 ms -2.0%
SwissDictionaryBenchmark.SwissDictionary_Insert(ItemCount: 100000) 4.06 ms 79.58 μs 4.15 ms -2.1%
SmallDictionaryBenchmark.Dictionary_Lookup(ItemCount: 8) 37.0 ns 0.0 ns 37.0 ns -0.0%
SmallDictionaryBenchmark.SmallDictionary_Lookup(ItemCount: 8) 22.4 ns 0.2 ns 22.4 ns +0.2%
SmallDictionaryBenchmark.Dictionary_Lookup(ItemCount: 64) 297.4 ns 0.6 ns 295.6 ns +0.6%
SmallDictionaryBenchmark.SmallDictionary_Lookup(ItemCount: 64) 1.13 μs 10.2 ns 1.11 μs +1.1%
SwissDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.74 μs 10.5 ns 4.73 μs +0.1%
SwissDictionaryBenchmark.SwissDictionary_Lookup(ItemCount: 1000) 3.01 μs 13.7 ns 2.99 μs +0.7%
SwissDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.63 ms 2.34 μs 1.63 ms -0.2%
SwissDictionaryBenchmark.SwissDictionary_Lookup(ItemCount: 100000) 832.83 μs 2.38 μs 824.67 μs +1.0%
FenwickTreeBenchmark.Array_Mixed(ItemCount: 1000) 183.84 μs 1.60 μs n/a 🆕 new
FenwickTreeBenchmark.FenwickTree_Mixed(ItemCount: 1000) 11.33 μs 23.6 ns n/a 🆕 new
FenwickTreeBenchmark.Array_Mixed(ItemCount: 100000) 167.13 ms 699.53 μs n/a 🆕 new
FenwickTreeBenchmark.FenwickTree_Mixed(ItemCount: 100000) 637.62 μs 9.92 μs n/a 🆕 new
FenwickTreeBenchmark.Array_RangeSum(ItemCount: 1000) 221.59 μs 410.1 ns n/a 🆕 new
FenwickTreeBenchmark.FenwickTree_RangeSum(ItemCount: 1000) 7.45 μs 3.9 ns n/a 🆕 new
FenwickTreeBenchmark.Array_RangeSum(ItemCount: 100000) 158.91 ms 140.71 μs n/a 🆕 new
FenwickTreeBenchmark.FenwickTree_RangeSum(ItemCount: 100000) 328.19 μs 1.02 μs n/a 🆕 new
SmallDictionaryBenchmark.Dictionary_Remove(ItemCount: 8) 1.33 μs 73.3 ns 1.51 μs -12.3%
SmallDictionaryBenchmark.SmallDictionary_Remove(ItemCount: 8) 1.23 μs 64.0 ns 1.52 μs -19.2%
SmallDictionaryBenchmark.Dictionary_Remove(ItemCount: 64) 5.25 μs 675.2 ns 5.50 μs -4.6%
SmallDictionaryBenchmark.SmallDictionary_Remove(ItemCount: 64) 22.45 μs 1.09 μs 26.90 μs -16.6%
SwissDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 87.14 μs 9.84 μs 88.97 μs -2.1%
SwissDictionaryBenchmark.SwissDictionary_Remove(ItemCount: 1000) 76.52 μs 6.07 μs 81.37 μs -6.0%
HashCachingSetBenchmark.HashSet_Remove(ItemCount: 1000) 29.26 μs 2.11 μs 29.14 μs +0.4%
HashCachingSetBenchmark.HashCachingSet_Remove(ItemCount: 1000) 117.95 μs 6.09 μs 120.10 μs -1.8%
SwissDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.11 ms 28.68 μs 2.12 ms -0.8%
SwissDictionaryBenchmark.SwissDictionary_Remove(ItemCount: 100000) 2.00 ms 404.16 μs 1.92 ms +4.3%
HashCachingSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.76 ms 31.91 μs 1.88 ms -6.5%
HashCachingSetBenchmark.HashCachingSet_Remove(ItemCount: 100000) 1.68 ms 205.06 μs 1.78 ms -5.6%
EnumSetBenchmark.HashSet_Add 598.3 ns 4.7 ns 594.3 ns +0.7%
EnumSetBenchmark.EnumSet_Add 86.6 ns 0.6 ns 85.8 ns +0.9%
CeleritySetBenchmark.HashSet_Add(ItemCount: 1000) 13.36 μs 478.8 ns 13.56 μs -1.5%
SparseSetBenchmark.HashSet_Add(ItemCount: 1000) 7.09 μs 89.3 ns 7.00 μs +1.3%
CeleritySetBenchmark.CeleritySet_Add(ItemCount: 1000) 9.62 μs 139.0 ns 9.41 μs +2.2%
SparseSetBenchmark.SparseSet_Add(ItemCount: 1000) 6.92 μs 183.9 ns 6.83 μs +1.3%
CeleritySetBenchmark.HashSet_Add(ItemCount: 100000) 5.30 ms 120.80 μs 5.24 ms +1.3%
SparseSetBenchmark.HashSet_Add(ItemCount: 100000) 1.71 ms 33.82 μs 1.70 ms +0.6%
CeleritySetBenchmark.CeleritySet_Add(ItemCount: 100000) 3.51 ms 30.89 μs 3.52 ms -0.2%
SparseSetBenchmark.SparseSet_Add(ItemCount: 100000) 1.61 ms 79.61 μs 1.49 ms +7.7%
SparseSetBenchmark.HashSet_ClearRefill(ItemCount: 1000) 6.12 μs 12.0 ns 6.12 μs -0.1%
SparseSetBenchmark.SparseSet_ClearRefill(ItemCount: 1000) 5.02 μs 21.2 ns 4.99 μs +0.5%
SparseSetBenchmark.HashSet_ClearRefill(ItemCount: 100000) 1.28 ms 6.64 μs 1.28 ms +0.3%
SparseSetBenchmark.SparseSet_ClearRefill(ItemCount: 100000) 678.29 μs 4.01 μs 685.79 μs -1.1%
EnumSetBenchmark.HashSet_Contains 170.4 ns 0.2 ns 170.8 ns -0.3%
EnumSetBenchmark.EnumSet_Contains 51.5 ns 0.1 ns 51.5 ns +0.0%
CeleritySetBenchmark.HashSet_Contains(ItemCount: 1000) 4.85 μs 147.6 ns 4.71 μs +3.0%
SparseSetBenchmark.HashSet_Contains(ItemCount: 1000) 4.62 μs 8.1 ns 4.62 μs -0.1%
CeleritySetBenchmark.CeleritySet_Contains(ItemCount: 1000) 1.93 μs 21.0 ns 1.92 μs +0.5%
SparseSetBenchmark.SparseSet_Contains(ItemCount: 1000) 1.43 μs 1.7 ns 1.43 μs +0.1%
CeleritySetBenchmark.HashSet_Contains(ItemCount: 100000) 1.52 ms 8.31 μs 1.51 ms +0.4%
SparseSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.27 ms 8.89 μs 1.27 ms -0.0%
CeleritySetBenchmark.CeleritySet_Contains(ItemCount: 100000) 547.10 μs 3.05 μs 549.80 μs -0.5%
SparseSetBenchmark.SparseSet_Contains(ItemCount: 100000) 263.41 μs 1.04 μs 262.22 μs +0.5%
LruCacheBenchmark.Dictionary_Get(ItemCount: 1000) 24.35 μs 461.0 ns 24.87 μs -2.1%
LruCacheBenchmark.LruCache_Get(ItemCount: 1000) 7.34 μs 21.5 ns 7.49 μs -2.0%
LruCacheBenchmark.Dictionary_Get(ItemCount: 100000) 25.20 μs 375.7 ns 25.01 μs +0.8%
LruCacheBenchmark.LruCache_Get(ItemCount: 100000) 7.52 μs 9.9 ns 7.62 μs -1.4%
LruCacheBenchmark.Dictionary_GetMissing(ItemCount: 1000) 4.61 μs 25.8 ns 4.59 μs +0.5%
LruCacheBenchmark.LruCache_GetMissing(ItemCount: 1000) 3.62 μs 3.4 ns 3.62 μs +0.0%
LruCacheBenchmark.Dictionary_GetMissing(ItemCount: 100000) 560.63 μs 23.08 μs 539.63 μs +3.9%
LruCacheBenchmark.LruCache_GetMissing(ItemCount: 100000) 1.55 ms 742.4 ns 1.55 ms -0.0%
HashCachingDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 14.22 μs 276.6 ns 13.95 μs +1.9%
HashCachingDictionaryBenchmark.HashCachingDictionary_Insert(ItemCount: 1000) 10.91 μs 155.1 ns 10.63 μs +2.6%
HashCachingDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 5.08 ms 91.26 μs 5.08 ms +0.0%
HashCachingDictionaryBenchmark.HashCachingDictionary_Insert(ItemCount: 100000) 7.24 ms 278.89 μs 7.22 ms +0.3%
HashCachingDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.83 μs 5.1 ns 4.60 μs +5.0%
HashCachingDictionaryBenchmark.HashCachingDictionary_Lookup(ItemCount: 1000) 2.88 μs 31.5 ns 2.85 μs +1.0%
HashCachingDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.61 ms 2.49 μs 1.61 ms +0.3%
HashCachingDictionaryBenchmark.HashCachingDictionary_Lookup(ItemCount: 100000) 826.36 μs 8.01 μs 830.44 μs -0.5%
LruCacheBenchmark.Dictionary_Put(ItemCount: 1000) 95.22 μs 4.94 μs 89.54 μs +6.3%
LruCacheBenchmark.LruCache_Put(ItemCount: 1000) 484.11 μs 20.87 μs 475.29 μs +1.9%
LruCacheBenchmark.Dictionary_Put(ItemCount: 100000) 7.80 ms 74.63 μs 7.80 ms -0.1%
LruCacheBenchmark.LruCache_Put(ItemCount: 100000) 6.45 ms 17.00 μs 6.43 ms +0.3%
EnumSetBenchmark.HashSet_Remove 3.04 μs 457.9 ns 2.35 μs +29.6% ⚠️
EnumSetBenchmark.EnumSet_Remove 1.07 μs 174.8 ns 942.4 ns +13.9%
CeleritySetBenchmark.HashSet_Remove(ItemCount: 1000) 28.94 μs 2.35 μs 29.69 μs -2.5%
HashCachingDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 81.75 μs 8.14 μs 77.96 μs +4.9%
CeleritySetBenchmark.CeleritySet_Remove(ItemCount: 1000) 144.79 μs 6.99 μs 141.43 μs +2.4%
HashCachingDictionaryBenchmark.HashCachingDictionary_Remove(ItemCount: 1000) 116.63 μs 10.55 μs 114.67 μs +1.7%
SparseSetBenchmark.HashSet_Remove(ItemCount: 1000) 26.73 μs 1.33 μs 29.04 μs -7.9%
SparseSetBenchmark.SparseSet_Remove(ItemCount: 1000) 24.53 μs 2.50 μs 22.52 μs +8.9%
CeleritySetBenchmark.HashSet_Remove(ItemCount: 100000) 1.86 ms 123.91 μs 1.70 ms +9.5%
HashCachingDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.04 ms 80.88 μs 2.01 ms +1.4%
CeleritySetBenchmark.CeleritySet_Remove(ItemCount: 100000) 1.64 ms 163.26 μs 1.55 ms +5.9%
HashCachingDictionaryBenchmark.HashCachingDictionary_Remove(ItemCount: 100000) 2.26 ms 254.19 μs 2.20 ms +2.5%
SparseSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.49 ms 17.95 μs 1.49 ms +0.4%
SparseSetBenchmark.SparseSet_Remove(ItemCount: 100000) 662.09 μs 17.52 μs 649.36 μs +2.0%
EnumSetBenchmark.HashSet_Union 445.7 ns 7.5 ns 421.2 ns +5.8%
EnumSetBenchmark.EnumSet_Union 23.7 ns 0.7 ns 22.9 ns +3.4%
CuckooFilterBenchmark.HashSet_Add(ItemCount: 1000) 12.24 μs 113.2 ns 13.74 μs -10.9% ✅
HyperLogLogBenchmark.HashSet_Add(ItemCount: 1000) 13.63 μs 396.4 ns 13.43 μs +1.5%
PooledCeleritySetBenchmark.HashSet_Add(ItemCount: 1000) 13.85 μs 97.3 ns 13.84 μs +0.0%
CuckooFilterBenchmark.CuckooFilter_Add(ItemCount: 1000) 7.24 μs 27.2 ns 7.32 μs -1.1%
HyperLogLogBenchmark.HyperLogLog_Add(ItemCount: 1000) 27.53 μs 32.6 ns 27.47 μs +0.2%
PooledCeleritySetBenchmark.PooledCeleritySet_Add(ItemCount: 1000) 8.97 μs 57.4 ns 8.81 μs +1.8%
CuckooFilterBenchmark.HashSet_Add(ItemCount: 100000) 4.75 ms 124.72 μs 4.77 ms -0.4%
HyperLogLogBenchmark.HashSet_Add(ItemCount: 100000) 4.88 ms 62.44 μs 4.93 ms -0.9%
PooledCeleritySetBenchmark.HashSet_Add(ItemCount: 100000) 5.00 ms 277.35 μs 4.90 ms +2.0%
CuckooFilterBenchmark.CuckooFilter_Add(ItemCount: 100000) 2.09 ms 13.14 μs 2.10 ms -0.4%
HyperLogLogBenchmark.HyperLogLog_Add(ItemCount: 100000) 575.77 μs 351.4 ns 577.20 μs -0.2%
PooledCeleritySetBenchmark.PooledCeleritySet_Add(ItemCount: 100000) 2.93 ms 6.45 μs 2.90 ms +0.9%
CuckooFilterBenchmark.HashSet_Contains(ItemCount: 1000) 4.73 μs 20.7 ns 4.72 μs +0.2%
PooledCeleritySetBenchmark.HashSet_Contains(ItemCount: 1000) 4.96 μs 218.0 ns 4.74 μs +4.6%
CuckooFilterBenchmark.CuckooFilter_Contains(ItemCount: 1000) 4.94 μs 40.2 ns 4.91 μs +0.6%
PooledCeleritySetBenchmark.PooledCeleritySet_Contains(ItemCount: 1000) 1.92 μs 29.0 ns 1.93 μs -0.6%
CuckooFilterBenchmark.HashSet_Contains(ItemCount: 100000) 1.60 ms 11.67 μs 1.57 ms +1.6%
PooledCeleritySetBenchmark.HashSet_Contains(ItemCount: 100000) 1.58 ms 8.99 μs 1.59 ms -0.3%
CuckooFilterBenchmark.CuckooFilter_Contains(ItemCount: 100000) 1.74 ms 1.55 μs 1.74 ms -0.0%
PooledCeleritySetBenchmark.PooledCeleritySet_Contains(ItemCount: 100000) 576.04 μs 1.83 μs 577.14 μs -0.2%
CuckooFilterBenchmark.HashSet_ContainsMissing(ItemCount: 1000) 4.54 μs 3.1 ns 4.54 μs -0.0%
CuckooFilterBenchmark.CuckooFilter_ContainsMissing(ItemCount: 1000) 8.36 μs 7.1 ns 8.37 μs -0.1%
CuckooFilterBenchmark.HashSet_ContainsMissing(ItemCount: 100000) 1.96 ms 6.15 μs 1.97 ms -0.6%
CuckooFilterBenchmark.CuckooFilter_ContainsMissing(ItemCount: 100000) 930.10 μs 1.89 μs 930.88 μs -0.1%
DequeBenchmark.LinkedList_Enumerate(ItemCount: 1000) 1.37 μs 0.8 ns 1.37 μs -0.1%
DequeBenchmark.Deque_Enumerate(ItemCount: 1000) 941.7 ns 0.4 ns 942.1 ns -0.0%
DequeBenchmark.LinkedList_Enumerate(ItemCount: 100000) 135.07 μs 118.7 ns 135.16 μs -0.1%
DequeBenchmark.Deque_Enumerate(ItemCount: 100000) 93.85 μs 78.9 ns 93.89 μs -0.0%
HyperLogLogBenchmark.HashSet_Estimate(ItemCount: 1000) 0.0 ns 0.0 ns 0.0 ns -75.5%
HyperLogLogBenchmark.HyperLogLog_Estimate(ItemCount: 1000) 23.44 μs 9.5 ns 23.46 μs -0.1%
HyperLogLogBenchmark.HashSet_Estimate(ItemCount: 100000) 0.0 ns 0.0 ns 0.0 ns +63.9%
HyperLogLogBenchmark.HyperLogLog_Estimate(ItemCount: 100000) 22.98 μs 8.9 ns 22.98 μs +0.0%
PooledCelerityDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 15.46 μs 424.0 ns 15.51 μs -0.3%
PooledCelerityDictionaryBenchmark.PooledCelerityDictionary_Insert(ItemCount: 1000) 8.44 μs 323.1 ns 8.37 μs +0.8%
PooledCelerityDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 5.04 ms 101.38 μs 5.07 ms -0.7%
PooledCelerityDictionaryBenchmark.PooledCelerityDictionary_Insert(ItemCount: 100000) 3.01 ms 6.40 μs 3.03 ms -0.4%
PooledCelerityDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.72 μs 20.5 ns 4.72 μs +0.0%
PooledCelerityDictionaryBenchmark.PooledCelerityDictionary_Lookup(ItemCount: 1000) 2.38 μs 16.4 ns 2.33 μs +2.2%
PooledCelerityDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.61 ms 4.52 μs 1.62 ms -0.8%
PooledCelerityDictionaryBenchmark.PooledCelerityDictionary_Lookup(ItemCount: 100000) 724.71 μs 28.01 μs 701.59 μs +3.3%
DequeBenchmark.LinkedList_PushFront(ItemCount: 1000) 44.71 μs 5.97 μs 35.36 μs +26.5% ⚠️
DequeBenchmark.Deque_PushFront(ItemCount: 1000) 27.14 μs 4.03 μs 24.44 μs +11.0%
DequeBenchmark.LinkedList_PushFront(ItemCount: 100000) 1.89 ms 46.55 μs 1.92 ms -1.6%
DequeBenchmark.Deque_PushFront(ItemCount: 100000) 760.32 μs 184.87 μs 689.23 μs +10.3%
DequeBenchmark.LinkedList_Queue(ItemCount: 1000) 53.98 μs 1.85 μs 57.93 μs -6.8%
DequeBenchmark.Deque_Queue(ItemCount: 1000) 35.55 μs 3.62 μs 40.46 μs -12.1%
DequeBenchmark.LinkedList_Queue(ItemCount: 100000) 4.37 ms 54.17 μs 4.37 ms +0.1%
DequeBenchmark.Deque_Queue(ItemCount: 100000) 455.67 μs 83.18 μs 455.90 μs -0.0%
PooledCelerityDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 80.50 μs 7.13 μs 87.28 μs -7.8%
PooledCeleritySetBenchmark.HashSet_Remove(ItemCount: 1000) 32.41 μs 3.83 μs 33.65 μs -3.7%
PooledCelerityDictionaryBenchmark.PooledCelerityDictionary_Remove(ItemCount: 1000) 149.88 μs 6.39 μs 147.79 μs +1.4%
PooledCeleritySetBenchmark.PooledCeleritySet_Remove(ItemCount: 1000) 136.68 μs 3.02 μs 144.25 μs -5.2%
CuckooFilterBenchmark.HashSet_Remove(ItemCount: 1000) 13.56 μs 84.5 ns 13.65 μs -0.7%
CuckooFilterBenchmark.CuckooFilter_Remove(ItemCount: 1000) 12.96 μs 30.1 ns 12.93 μs +0.2%
PooledCelerityDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.12 ms 34.02 μs 2.22 ms -4.5%
PooledCeleritySetBenchmark.HashSet_Remove(ItemCount: 100000) 1.80 ms 56.47 μs 1.95 ms -7.5%
PooledCelerityDictionaryBenchmark.PooledCelerityDictionary_Remove(ItemCount: 100000) 1.66 ms 145.05 μs 1.66 ms +0.2%
PooledCeleritySetBenchmark.PooledCeleritySet_Remove(ItemCount: 100000) 1.31 ms 74.59 μs 1.39 ms -5.6%
CuckooFilterBenchmark.HashSet_Remove(ItemCount: 100000) 3.87 ms 45.06 μs 3.98 ms -2.9%
CuckooFilterBenchmark.CuckooFilter_Remove(ItemCount: 100000) 3.97 ms 7.08 μs 3.98 ms -0.1%
CountMinSketchBenchmark.Dictionary_Add(ItemCount: 1000) 10.54 μs 21.5 ns 10.60 μs -0.5%
IntSetBenchmark.HashSet_Add(ItemCount: 1000) 12.18 μs 231.4 ns 12.33 μs -1.2%
CountMinSketchBenchmark.CountMinSketch_Add(ItemCount: 1000) 10.47 μs 91.0 ns 10.70 μs -2.2%
IntSetBenchmark.IntSet_Add(ItemCount: 1000) 8.49 μs 61.2 ns 8.53 μs -0.5%
CountMinSketchBenchmark.Dictionary_Add(ItemCount: 100000) 1.44 ms 16.17 μs 1.45 ms -1.2%
IntSetBenchmark.HashSet_Add(ItemCount: 100000) 5.05 ms 100.59 μs 4.98 ms +1.4%
CountMinSketchBenchmark.CountMinSketch_Add(ItemCount: 100000) 929.72 μs 1.39 μs 927.79 μs +0.2%
IntSetBenchmark.IntSet_Add(ItemCount: 100000) 3.17 ms 20.38 μs 3.18 ms -0.1%
BitSetBenchmark.BitArray_And(ItemCount: 1024) 86.5 ns 29.7 ns 57.5 ns +50.3%
BitSetBenchmark.BitSet_And(ItemCount: 1024) 1.21 μs 40.0 ns 1.21 μs +0.1%
BitSetBenchmark.BitArray_And(ItemCount: 1000000) 44.57 μs 439.3 ns 44.88 μs -0.7%
BitSetBenchmark.BitSet_And(ItemCount: 1000000) 4.48 ms 2.34 μs 4.48 ms -0.1%
DisjointSetBenchmark.Dictionary_Components(ItemCount: 1000) 18.76 μs 200.2 ns 18.66 μs +0.5%
DisjointSetBenchmark.DisjointSet_Components(ItemCount: 1000) 16.82 μs 218.8 ns 16.72 μs +0.6%
DisjointSetBenchmark.Dictionary_Components(ItemCount: 100000) 4.24 ms 96.06 μs 4.46 ms -4.9%
DisjointSetBenchmark.DisjointSet_Components(ItemCount: 100000) 3.13 ms 26.11 μs 3.19 ms -1.7%
DisjointSetBenchmark.Dictionary_Connected(ItemCount: 1000) 9.73 μs 432.0 ns 9.42 μs +3.2%
DisjointSetBenchmark.DisjointSet_Connected(ItemCount: 1000) 12.19 μs 93.0 ns 12.33 μs -1.2%
DisjointSetBenchmark.Dictionary_Connected(ItemCount: 100000) 257.93 μs 2.44 μs 261.80 μs -1.5%
DisjointSetBenchmark.DisjointSet_Connected(ItemCount: 100000) 320.67 μs 16.33 μs 347.83 μs -7.8%
IntSetBenchmark.HashSet_Contains(ItemCount: 1000) 4.73 μs 3.6 ns 4.75 μs -0.3%
IntSetBenchmark.IntSet_Contains(ItemCount: 1000) 1.80 μs 9.3 ns 1.80 μs -0.0%
IntSetBenchmark.HashSet_Contains(ItemCount: 100000) 1.55 ms 5.60 μs 1.54 ms +0.8%
IntSetBenchmark.IntSet_Contains(ItemCount: 100000) 573.33 μs 1.62 μs 578.22 μs -0.8%
CountMinSketchBenchmark.Dictionary_Estimate(ItemCount: 1000) 4.37 μs 1.7 ns 4.38 μs -0.2%
CountMinSketchBenchmark.CountMinSketch_Estimate(ItemCount: 1000) 10.07 μs 15.0 ns 10.07 μs -0.0%
CountMinSketchBenchmark.Dictionary_Estimate(ItemCount: 100000) 489.20 μs 3.39 μs 488.33 μs +0.2%
CountMinSketchBenchmark.CountMinSketch_Estimate(ItemCount: 100000) 2.00 ms 40.08 μs 1.97 ms +1.9%
IntDictionaryBenchmark.Dictionary_Insert(ItemCount: 1000) 13.93 μs 578.7 ns 13.14 μs +6.0%
IntDictionaryBenchmark.IntDictionary_Insert(ItemCount: 1000) 11.25 μs 201.7 ns 11.10 μs +1.4%
IntDictionaryBenchmark.Dictionary_Insert(ItemCount: 100000) 4.18 ms 64.97 μs 4.14 ms +0.8%
IntDictionaryBenchmark.IntDictionary_Insert(ItemCount: 100000) 4.83 ms 19.77 μs 4.80 ms +0.5%
IntDictionaryBenchmark.Dictionary_Lookup(ItemCount: 1000) 4.90 μs 149.5 ns 4.73 μs +3.6%
IntDictionaryBenchmark.IntDictionary_Lookup(ItemCount: 1000) 2.14 μs 4.5 ns 2.14 μs +0.1%
IntDictionaryBenchmark.Dictionary_Lookup(ItemCount: 100000) 1.60 ms 6.54 μs 1.60 ms -0.2%
IntDictionaryBenchmark.IntDictionary_Lookup(ItemCount: 100000) 678.35 μs 2.21 μs 677.09 μs +0.2%
BitSetBenchmark.BitArray_Or(ItemCount: 1024) 83.6 ns 29.8 ns 61.5 ns +35.9%
BitSetBenchmark.BitSet_Or(ItemCount: 1024) 1.25 μs 2.9 ns 1.25 μs -0.1%
BitSetBenchmark.BitArray_Or(ItemCount: 1000000) 44.34 μs 414.2 ns 44.15 μs +0.4%
BitSetBenchmark.BitSet_Or(ItemCount: 1000000) 4.48 ms 3.60 μs 4.48 ms -0.0%
BitSetBenchmark.BitArray_PopCount(ItemCount: 1024) 1.24 μs 4.0 ns 1.24 μs -0.0%
BitSetBenchmark.BitSet_PopCount(ItemCount: 1024) 7.0 ns 0.0 ns 7.1 ns -0.2%
BitSetBenchmark.BitArray_PopCount(ItemCount: 1000000) 4.82 ms 2.88 μs 4.83 ms -0.1%
BitSetBenchmark.BitSet_PopCount(ItemCount: 1000000) 4.94 μs 11.3 ns 4.94 μs -0.0%
IntDictionaryBenchmark.Dictionary_Remove(ItemCount: 1000) 82.37 μs 7.17 μs 81.68 μs +0.8%
IntSetBenchmark.HashSet_Remove(ItemCount: 1000) 34.30 μs 3.10 μs 28.04 μs +22.3%
IntDictionaryBenchmark.IntDictionary_Remove(ItemCount: 1000) 91.98 μs 6.67 μs 91.30 μs +0.7%
IntSetBenchmark.IntSet_Remove(ItemCount: 1000) 93.17 μs 3.86 μs 89.85 μs +3.7%
IntDictionaryBenchmark.Dictionary_Remove(ItemCount: 100000) 2.05 ms 16.37 μs 2.11 ms -3.0%
IntSetBenchmark.HashSet_Remove(ItemCount: 100000) 1.72 ms 12.51 μs 1.72 ms +0.2%
IntDictionaryBenchmark.IntDictionary_Remove(ItemCount: 100000) 2.89 ms 2.29 ms 1.64 ms +76.0%
IntSetBenchmark.IntSet_Remove(ItemCount: 100000) 1.33 ms 13.87 μs 1.34 ms -0.7%
DisjointSetBenchmark.Dictionary_Union(ItemCount: 1000) 97.07 μs 1.07 μs 101.84 μs -4.7%
DisjointSetBenchmark.DisjointSet_Union(ItemCount: 1000) 26.93 μs 143.8 ns 27.61 μs -2.5%
DisjointSetBenchmark.Dictionary_Union(ItemCount: 100000) 45.37 ms 1.85 ms 52.11 ms -12.9% ✅
DisjointSetBenchmark.DisjointSet_Union(ItemCount: 100000) 8.15 ms 58.61 μs 8.43 ms -3.3%
BitSetBenchmark.BitArray_Xor(ItemCount: 1024) 55.0 ns 0.4 ns 54.5 ns +1.0%
BitSetBenchmark.BitSet_Xor(ItemCount: 1024) 1.25 μs 7.7 ns 1.23 μs +1.5%
BitSetBenchmark.BitArray_Xor(ItemCount: 1000000) 44.41 μs 663.6 ns 44.45 μs -0.1%
BitSetBenchmark.BitSet_Xor(ItemCount: 1000000) 4.48 ms 6.32 μs 4.47 ms +0.1%
Hashers (100)
Benchmark This PR StdDev main Δ
StringHasherBenchmark.Bcl_GetHashCode(Shape: ShortAscii) 15.40 μs 9.8 ns 15.41 μs -0.1%
StringHasherBenchmark.EqualityComparer_Default(Shape: ShortAscii) 15.38 μs 15.9 ns 15.40 μs -0.1%
StringHasherBenchmark.Djb2(Shape: ShortAscii) 22.03 μs 20.8 ns 22.02 μs +0.1%
StringHasherBenchmark.Djb2A(Shape: ShortAscii) 22.04 μs 32.6 ns 22.03 μs +0.0%
StringHasherBenchmark.Sdbm(Shape: ShortAscii) 32.84 μs 20.4 ns 32.85 μs -0.0%
StringHasherBenchmark.Elf(Shape: ShortAscii) 43.89 μs 135.0 ns 43.98 μs -0.2%
StringHasherBenchmark.Crc32(Shape: ShortAscii) 49.90 μs 61.4 ns 49.89 μs +0.0%
StringHasherBenchmark.Adler32(Shape: ShortAscii) 96.21 μs 50.1 ns 96.21 μs -0.0%
StringHasherBenchmark.FnV1(Shape: ShortAscii) 25.34 μs 18.8 ns 25.35 μs -0.0%
StringHasherBenchmark.FnV1_64(Shape: ShortAscii) 25.48 μs 50.0 ns 25.48 μs +0.0%
StringHasherBenchmark.FnV1A(Shape: ShortAscii) 10.65 μs 27.2 ns 10.64 μs +0.1%
StringHasherBenchmark.FnV1A_Full(Shape: ShortAscii) 22.84 μs 16.9 ns 22.84 μs -0.0%
StringHasherBenchmark.FnV1A_64(Shape: ShortAscii) 25.73 μs 29.8 ns 25.72 μs +0.0%
StringHasherBenchmark.JenkinsOaat(Shape: ShortAscii) 39.29 μs 40.2 ns 39.28 μs +0.0%
StringHasherBenchmark.Murmur2(Shape: ShortAscii) 15.19 μs 38.1 ns 15.21 μs -0.1%
StringHasherBenchmark.Murmur3(Shape: ShortAscii) 17.32 μs 32.7 ns 17.34 μs -0.1%
StringHasherBenchmark.XxHash32(Shape: ShortAscii) 16.92 μs 15.0 ns 16.93 μs -0.1%
StringHasherBenchmark.XxHash64(Shape: ShortAscii) 18.11 μs 13.5 ns 18.08 μs +0.2%
StringHasherBenchmark.XxHash3(Shape: ShortAscii) 14.82 μs 15.0 ns 14.87 μs -0.3%
StringHasherBenchmark.CityHash64(Shape: ShortAscii) 15.23 μs 10.1 ns 15.22 μs +0.0%
StringHasherBenchmark.MetroHash64(Shape: ShortAscii) 15.01 μs 8.0 ns 15.01 μs -0.0%
StringHasherBenchmark.SipHash13(Shape: ShortAscii) 27.38 μs 15.7 ns 27.37 μs +0.0%
StringHasherBenchmark.SipHash24(Shape: ShortAscii) 38.48 μs 17.6 ns 38.48 μs -0.0%
StringHasherBenchmark.HalfSipHash24(Shape: ShortAscii) 50.00 μs 43.1 ns 49.99 μs +0.0%
StringHasherBenchmark.HighwayHash64(Shape: ShortAscii) 480.21 μs 246.7 ns 479.60 μs +0.1%
StringHasherBenchmark.Bcl_GetHashCode(Shape: LongAscii) 96.91 μs 55.5 ns 96.88 μs +0.0%
StringHasherBenchmark.EqualityComparer_Default(Shape: LongAscii) 96.62 μs 48.6 ns 96.56 μs +0.1%
StringHasherBenchmark.Djb2(Shape: LongAscii) 229.32 μs 53.2 ns 229.31 μs +0.0%
StringHasherBenchmark.Djb2A(Shape: LongAscii) 229.28 μs 40.5 ns 229.35 μs -0.0%
StringHasherBenchmark.Sdbm(Shape: LongAscii) 308.08 μs 158.1 ns 308.09 μs -0.0%
StringHasherBenchmark.Elf(Shape: LongAscii) 577.53 μs 248.8 ns 577.51 μs +0.0%
StringHasherBenchmark.Crc32(Shape: LongAscii) 589.59 μs 102.3 ns 589.64 μs -0.0%
StringHasherBenchmark.Adler32(Shape: LongAscii) 789.16 μs 132.1 ns 788.97 μs +0.0%
StringHasherBenchmark.FnV1(Shape: LongAscii) 289.51 μs 76.9 ns 289.58 μs -0.0%
StringHasherBenchmark.FnV1_64(Shape: LongAscii) 284.40 μs 77.5 ns 284.53 μs -0.0%
StringHasherBenchmark.FnV1A(Shape: LongAscii) 122.25 μs 43.9 ns 122.32 μs -0.1%
StringHasherBenchmark.FnV1A_Full(Shape: LongAscii) 281.66 μs 64.4 ns 281.58 μs +0.0%
StringHasherBenchmark.FnV1A_64(Shape: LongAscii) 286.80 μs 52.8 ns 286.89 μs -0.0%
StringHasherBenchmark.JenkinsOaat(Shape: LongAscii) 378.50 μs 124.4 ns 378.55 μs -0.0%
StringHasherBenchmark.Murmur2(Shape: LongAscii) 101.93 μs 57.8 ns 101.93 μs -0.0%
StringHasherBenchmark.Murmur3(Shape: LongAscii) 112.95 μs 60.3 ns 112.19 μs +0.7%
StringHasherBenchmark.XxHash32(Shape: LongAscii) 73.34 μs 154.8 ns 73.22 μs +0.2%
StringHasherBenchmark.XxHash64(Shape: LongAscii) 91.24 μs 1.07 μs 90.66 μs +0.6%
StringHasherBenchmark.XxHash3(Shape: LongAscii) 70.06 μs 118.5 ns 70.30 μs -0.3%
StringHasherBenchmark.CityHash64(Shape: LongAscii) 123.05 μs 1.28 μs 122.42 μs +0.5%
StringHasherBenchmark.MetroHash64(Shape: LongAscii) 77.93 μs 2.45 μs 77.41 μs +0.7%
StringHasherBenchmark.SipHash13(Shape: LongAscii) 114.60 μs 50.1 ns 114.71 μs -0.1%
StringHasherBenchmark.SipHash24(Shape: LongAscii) 160.91 μs 611.2 ns 161.22 μs -0.2%
StringHasherBenchmark.HalfSipHash24(Shape: LongAscii) 247.49 μs 256.6 ns 247.59 μs -0.0%
StringHasherBenchmark.HighwayHash64(Shape: LongAscii) 836.27 μs 2.69 μs 837.90 μs -0.2%
StringHasherBenchmark.Bcl_GetHashCode(Shape: NonAscii) 22.88 μs 10.9 ns 22.87 μs +0.1%
StringHasherBenchmark.EqualityComparer_Default(Shape: NonAscii) 22.84 μs 16.2 ns 22.82 μs +0.1%
StringHasherBenchmark.Djb2(Shape: NonAscii) 43.42 μs 27.2 ns 43.44 μs -0.0%
StringHasherBenchmark.Djb2A(Shape: NonAscii) 43.42 μs 47.2 ns 43.43 μs -0.0%
StringHasherBenchmark.Sdbm(Shape: NonAscii) 61.56 μs 25.5 ns 61.59 μs -0.1%
StringHasherBenchmark.Elf(Shape: NonAscii) 99.75 μs 152.1 ns 99.70 μs +0.1%
StringHasherBenchmark.Crc32(Shape: NonAscii) 102.63 μs 64.9 ns 102.65 μs -0.0%
StringHasherBenchmark.Adler32(Shape: NonAscii) 175.39 μs 49.6 ns 175.45 μs -0.0%
StringHasherBenchmark.FnV1(Shape: NonAscii) 47.63 μs 28.6 ns 47.64 μs -0.0%
StringHasherBenchmark.FnV1_64(Shape: NonAscii) 49.99 μs 42.9 ns 50.07 μs -0.1%
StringHasherBenchmark.FnV1A(Shape: NonAscii) 21.65 μs 35.9 ns 21.65 μs +0.0%
StringHasherBenchmark.FnV1A_Full(Shape: NonAscii) 45.06 μs 39.3 ns 45.12 μs -0.1%
StringHasherBenchmark.FnV1A_64(Shape: NonAscii) 49.63 μs 41.2 ns 49.63 μs -0.0%
StringHasherBenchmark.JenkinsOaat(Shape: NonAscii) 73.11 μs 60.7 ns 73.13 μs -0.0%
StringHasherBenchmark.Murmur2(Shape: NonAscii) 27.23 μs 200.4 ns 27.24 μs -0.0%
StringHasherBenchmark.Murmur3(Shape: NonAscii) 31.85 μs 88.1 ns 31.88 μs -0.1%
StringHasherBenchmark.XxHash32(Shape: NonAscii) 23.91 μs 11.7 ns 24.07 μs -0.7%
StringHasherBenchmark.XxHash64(Shape: NonAscii) 29.12 μs 15.3 ns 29.15 μs -0.1%
StringHasherBenchmark.XxHash3(Shape: NonAscii) 22.90 μs 59.7 ns 22.92 μs -0.1%
StringHasherBenchmark.CityHash64(Shape: NonAscii) 22.02 μs 13.7 ns 22.03 μs -0.0%
StringHasherBenchmark.MetroHash64(Shape: NonAscii) 25.63 μs 8.3 ns 25.64 μs -0.0%
StringHasherBenchmark.SipHash13(Shape: NonAscii) 36.95 μs 99.4 ns 37.02 μs -0.2%
StringHasherBenchmark.SipHash24(Shape: NonAscii) 51.41 μs 625.4 ns 51.56 μs -0.3%
StringHasherBenchmark.HalfSipHash24(Shape: NonAscii) 73.22 μs 504.2 ns 73.15 μs +0.1%
StringHasherBenchmark.HighwayHash64(Shape: NonAscii) 516.43 μs 796.8 ns 515.53 μs +0.2%
IntegerHasherBenchmark.Guid_Bcl 1.16 μs 25.5 ns 1.17 μs -0.5%
IntegerHasherBenchmark.Guid_EqualityComparer 2.67 μs 3.5 ns 2.70 μs -1.1%
IntegerHasherBenchmark.Guid_Celerity 7.09 μs 11.2 ns 7.07 μs +0.2%
IntegerHasherBenchmark.Int32_Bcl 599.4 ns 1.1 ns 598.2 ns +0.2%
IntegerHasherBenchmark.Int32_EqualityComparer 592.0 ns 4.9 ns 589.8 ns +0.4%
IntegerHasherBenchmark.Int32_Identity 592.3 ns 2.2 ns 592.3 ns -0.0%
IntegerHasherBenchmark.Int32_WangNaive 1.10 μs 1.4 ns 1.10 μs -0.1%
IntegerHasherBenchmark.Int32_Wang 2.65 μs 5.6 ns 2.65 μs +0.2%
IntegerHasherBenchmark.Int32_Murmur3 2.37 μs 1.6 ns 2.37 μs +0.1%
IntegerHasherBenchmark.Int64_Bcl 1.21 μs 4.3 ns 1.21 μs -0.1%
IntegerHasherBenchmark.Int64_EqualityComparer 1.10 μs 1.6 ns 1.10 μs +0.0%
IntegerHasherBenchmark.Int64_Identity 593.0 ns 0.5 ns 592.6 ns +0.1%
IntegerHasherBenchmark.Int64_WangNaive 1.63 μs 2.4 ns 1.63 μs +0.0%
IntegerHasherBenchmark.Int64_Wang 3.66 μs 7.1 ns 3.65 μs +0.0%
IntegerHasherBenchmark.Int64_Murmur3 2.08 μs 3.4 ns 2.08 μs -0.1%
IntegerHasherBenchmark.UInt32_Bcl 599.0 ns 0.9 ns 598.5 ns +0.1%
IntegerHasherBenchmark.UInt32_EqualityComparer 590.1 ns 1.2 ns 590.8 ns -0.1%
IntegerHasherBenchmark.UInt32_Default 1.10 μs 1.9 ns 1.10 μs -0.0%
IntegerHasherBenchmark.UInt32_Wang 2.65 μs 3.7 ns 2.65 μs -0.3%
IntegerHasherBenchmark.UInt32_Murmur3 2.37 μs 2.1 ns 2.37 μs +0.0%
IntegerHasherBenchmark.UInt64_Bcl 1.21 μs 3.3 ns 1.21 μs +0.3%
IntegerHasherBenchmark.UInt64_EqualityComparer 1.10 μs 2.8 ns 1.10 μs -0.2%
IntegerHasherBenchmark.UInt64_Default 2.08 μs 0.7 ns 2.08 μs -0.0%
IntegerHasherBenchmark.UInt64_Wang 3.65 μs 6.0 ns 3.65 μs -0.1%
IntegerHasherBenchmark.UInt64_WangNaive 1.63 μs 2.4 ns 1.63 μs +0.1%

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

Resolves conflicts with the Trie (#286) and SparseSet (#288) merges, which
touched the same shared parity files. Every conflict was a "keep both, in
order" resolution — no content from either side was dropped:

- Program.cs           — CoreBenchmarks keeps Trie + Fenwick (SparseSet auto-merged)
- web/index.html       — ship cards for both Trie and FenwickTree
- web/dev/bench/*.html — COLLECTIONS entries for both
- README.md            — both the "Prefix trees" and "Prefix sums" groups, both
                         details blocks, and both decision-table rows (keeping
                         main's updated iteration-order row that mentions Trie)
- docs/api/collections.md — main's Trie section plus the FenwickTree section
- CHANGELOG.md         — all three Added entries plus main's new Fixed section

Also condenses the FenwickTree changelog entry to a single user-facing bullet,
matching the convention main just applied to the Trie and SparseSet entries.

Full suite green after the merge: 4413 passed, 0 failed.
Copilot AI review requested due to automatic review settings July 24, 2026 21:58

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (2)

src/Celerity/Collections/FenwickTree.cs:78

  • The IEnumerable constructor also allocates _tree with seed.Length + 1; if seed.Length == Array.MaxLength this will exceed the array ceiling. Add a guard with a clear exception before allocating.
        T[] seed = values as T[] ?? values.ToArray();
        _length = seed.Length;
        _tree = new T[_length + 1];

src/Celerity/Collections/FenwickTree.cs:210

  • AddCore always walks the tree and bumps _version even when delta is zero. Early-returning on delta == T.Zero avoids pointless work and prevents no-op updates from invalidating enumerators.
        for (int k = index + 1; k <= _length; k += k & -k)
            _tree[k] += delta;

        _version++;

Comment thread src/Celerity/Collections/FenwickTree.cs
Comment thread src/Celerity/Collections/FenwickTree.cs
Comment thread web/dev/bench/index.html Outdated
Comment thread CHANGELOG.md
…ersion bumps

- Guard the construction length against Array.MaxLength - 1 (the 1-based layout
  reserves one slot), so an oversized length throws a clear
  ArgumentOutOfRangeException instead of overflowing into an
  OverflowException / OutOfMemoryException from the allocation. The
  IEnumerable ctor gets the matching ArgumentException guard.
- A no-op update no longer bumps _version: AddCore returns early on a zero
  delta, which also covers the indexer setter (it reaches AddCore with
  `value - current`, zero exactly when the assigned value is already stored).
  This matches the rest of the library, where an operation that does not change
  the observable state does not invalidate active enumerators. It also skips the
  now-pointless O(log n) walk.
- Fix the inaccurate baseline comment on the bench dashboard: for a plain array
  holding raw values, point updates are O(1) and prefix/range sums are O(n) —
  not O(n) for both.
- Regression tests for all three, plus XML-doc and docs/api/collections.md
  updates so the no-op contract and the length ceiling are documented.

Full suite green: 4417 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 22:23
Every sibling collection (Deque, DisjointSet, IndexedPriorityQueue, LruCache,
SparseSet, ...) has a block in Celerity.AotSmokeTest; FenwickTree was missing
one. It is the only collection built on generic math, so its INumber<T> static
abstract members resolve through constrained calls the AOT compiler has to
specialize per T — worth pinning under Native AOT rather than JIT only. The
block exercises the O(n) seeded build, point update, prefix / range sums, the
indexer round-trip, the no-op update path, clear-then-reuse and the struct
enumerator, over two distinct T instantiations (long and int).

Verified: "Celerity AOT smoke test: all checks passed."

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Comment thread web/dev/bench/index.html Outdated
Comment thread web/dev/bench/detail.html Outdated
Copilot AI review requested due to automatic review settings July 24, 2026 22:29
…line

The bench dashboards split each benchmark's arms into a baseline/compare pair via
parseName(), which marks an arm as the baseline only when its <TypeName>_<Op>
type name is in BCL_TYPES. FenwickTree's baseline arms are named Array_* (a raw
long[] — the BCL has no prefix-sum type, so that is the honest reference), and
'Array' was not in the set, so both Array_* and FenwickTree_* landed in the
'celerity' slot: no pair could form and the FenwickTree card would have rendered
without a baseline or speedup.

Adds 'Array' to BCL_TYPES in web/dev/bench/index.html and detail.html, with a
comment explaining why the entry exists. Array_* is used only by
FenwickTreeBenchmark, so no existing arm changes classification.

web/index.html is deliberately untouched: its narrower BCL_TYPES feeds only the
homepage headline, which aggregates Lookup/Contains/Insert/Add/Remove ops —
FenwickTree's Mixed/RangeSum ops never reach it.

Verified by replaying the dashboards' own parseName + pairing logic over the real
method names: both FenwickTree ops pair correctly after the change and fail to
pair before it, with DisjointSet unaffected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Comment thread src/Celerity/Collections/FenwickTree.cs Outdated
Comment thread src/Celerity.Tests/Collections/FenwickTreeDifferentialTests.cs Outdated
…unted source

The IEnumerable<T> constructor called ToArray() before enforcing the MaxLength
ceiling, so an ICollection<T> whose Count exceeds the ceiling failed the
allocation (OutOfMemoryException) instead of reporting the documented
ArgumentException — and every counted source paid for an intermediate array that
was then copied into the backing store.

A counted source is now length-checked first and copied straight into the 1-based
backing array via ICollection<T>.CopyTo(_tree, 1), which both fixes the exception
and drops one full-size allocation and copy for the common T[] / List<T> case.
Unknown-length sequences keep the materialize-then-check path. The `values as T[]`
special case is subsumed — T[] is an ICollection<T>.

Also corrects a stale comment in FenwickTreeDifferentialTests that referred to an
"O(n) span build"; there is no span constructor, the long[] goes in through the
IEnumerable<T> counted fast path.

New tests cover the empty-source boundary (CopyTo targets index 1 of a length-1
array) on both paths, and a counted source that is neither T[] nor List<T>
(SortedSet<int>) so the CopyTo path itself is exercised.

Full suite green: 4419 passed, 0 failed. AOT smoke test: all checks passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 22:40
…hrow helper

Replaces the hardcoded "values" literal in ThrowIfSourceTooLong with a
paramName argument supplied via nameof at each call site, so a future rename of
the constructor parameter cannot silently desync the ArgumentException's
ParamName. No behavioural change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

src/Celerity/Collections/FenwickTree.cs:256

  • AddCore’s update walk can overflow int for large trees: when k is a large power of two, k += k & -k can wrap negative, causing an infinite loop (because k <= _length stays true) or an invalid array access. Using uint (or long) for the loop index and low-bit math avoids overflow while keeping the array indexed by int.
        for (int k = index + 1; k <= _length; k += k & -k)
            _tree[k] += delta;

Comment thread src/Celerity/Collections/FenwickTree.cs
… overflow

Both Fenwick ascents advance by adding the lowest set bit of the current 1-based
index. At k == 1 << 30 the next index is 1 << 31, which overflows a signed int and
wraps to int.MinValue — a negative value that still satisfies the `<= _length`
bound, so the walk then indexed the backing array out of range and threw
IndexOutOfRangeException.

This is reachable, not theoretical: the length ceiling is Array.MaxLength - 1
(~2.1 billion), and the smallest INumber<T> is one byte, so a 2^30-element
FenwickTree<byte> is about 1 GiB and allocates without gcAllowVeryLargeObjects.

Widens the cursor in AddCore and the parent index in the O(n) build loop to long.
Both walks still terminate at _length, so the casts back to int are always in
range. The descending walks (PrefixSum / RangeSumCore) only ever strip bits and
cannot overflow, so they are unchanged.

Regression test Add_ShouldNotOverflowIndex_WhenTreeExceedsTwoToThe30 was confirmed
to throw IndexOutOfRangeException against the pre-fix code and pass after. It is
not skipped: the 1 GiB array is committed but never faulted in beyond the ~30
cells the ascent touches, so it runs in milliseconds.
Add_LowestSetBitAscent_ShouldNotWrapAtTwoToThe30 pins the same arithmetic with no
allocation at all.

Suite green on net8.0 and net10.0: 4421 passed, 0 skipped. AOT smoke test passes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 22:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread src/Celerity.Tests/Collections/FenwickTreeTests.cs
…mory

Addresses the review concern that the >2^30 index-overflow regression test could
be flaky on memory-capped CI environments.

The test still builds a real 2^30-element tree, because that is the only thing
that actually reproduces the bug: the overflow depends on _length, so no smaller
instance reaches the failing step. (The arithmetic-only companion test that
previously stood alongside it is removed — it re-evaluated the expression inline
in the test file and never touched FenwickTree, so it could not fail regardless
of the production code and offered no regression protection.)

Instead of skipping unconditionally, the test now carries [MemoryIntensiveFact],
a FactAttribute that consults GCMemoryInfo.TotalAvailableMemoryBytes at discovery
time (which reflects the container/cgroup limit where one applies) and demands 3x
headroom over the stated requirement. Environments with room run the check;
constrained ones report it skipped rather than failing, so it can never turn the
build red on resource grounds.

Both paths verified:
  normal run                        -> executes and passes (~17 ms)
  DOTNET_GCHeapHardLimit=0x10000000 -> "Skipped! Failed: 0, Passed: 0, Skipped: 1"

Suite green: 4420 passed, 0 skipped, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 24, 2026 23:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 5 comments.

Comment thread src/Celerity/Collections/FenwickTree.cs Outdated
Comment thread src/Celerity/Collections/FenwickTree.cs
Comment thread src/Celerity/Collections/FenwickTree.cs Outdated
Comment thread src/Celerity.Tests/MemoryIntensiveFactAttribute.cs
Comment thread src/Celerity.Tests/Collections/FenwickTreeTests.cs
…ing, harden the test attribute

Review round 6:

- Extract the descending prefix walk into a single private PrefixSumCore, now
  shared by PrefixSum, RangeSumCore, and the indexer getter. The bit-strip was
  written three times across two methods; centralizing it removes the drift risk.
  RangeSumCore becomes the obvious PrefixSumCore(end) - PrefixSumCore(start).
- Correct the "single n-element array" wording in the XML summary, the API
  reference and the README: the 1-based layout means the backing array holds
  n + 1 elements with index 0 unused. The substantive claim (one flat array, no
  per-node object overhead) is unchanged.
- MemoryIntensiveFactAttribute now rejects a non-positive requiredMegabytes with
  ArgumentOutOfRangeException — a non-positive threshold is meaningless and would
  silently force the test to run everywhere, the exact behaviour the attribute
  exists to prevent — and states the no-overflow intent with checked arithmetic.
- Tag the memory-intensive regression with [Trait("Category", "MemoryIntensive")]
  so CI can segregate it (a serial job, or --filter "Category!=MemoryIntensive")
  without it having to be opted out of by default.

Verified: full suite 4420 passed / 0 failed; trait exclusion drops exactly that
one test (4419); the memory gate still reports Skipped under
DOTNET_GCHeapHardLimit=0x10000000.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.

Comment thread src/Celerity/Collections/FenwickTree.cs Outdated
Both callers have already established that the prefix bound is in range, so
the public PrefixSum wrapper's check can never fail for them. MoveNext paid
it once per element and kept the throw path reachable inside the loop body;
Total's check compares _length against itself. Routing both at PrefixSumCore
keeps every internal caller on the same rule.

Behaviour is unchanged - covered by the existing enumeration, Total and
differential tests.
Copilot AI review requested due to automatic review settings July 25, 2026 01:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 13 changed files in this pull request and generated no new comments.

@marius-bughiu

marius-bughiu commented Jul 25, 2026

Copy link
Copy Markdown
Owner Author

CI status: green except one benchmark shard, which was cancelled by the job timeout — not a benchmark failure

Everything that gates correctness passed on 7f6fba1:

Check Result
build-and-test × Windows / Linux / macOS (net8/9/10)
coverage (line ≥ 95%, branch ≥ 90%)
aot-publish linux-x64 × net8.0 / net9.0 / net10.0
Copilot Code Review ✅ no new comments, 0 unresolved threads
benchmark shards 1–5
benchmark shard 0 ⚠️ cancelled at the 120-min job timeout
aggregate & report

What actually happened. Shard 0's step timings (job):

  • Run head benchmarks (shard 0) — 02:33:53 → 03:38:30 (65 min), succeeded, report staged.
  • Run base (main) benchmarks… — 03:38:30 → cut at 04:34:02, with only 55 min left of the 120-min budget.

So the PR-head measurements completed fine; the same-runner main baseline ran out of clock.

Why shard 0 specifically. Shard membership is greedy LPT bin-packing over the benchmark classes (Program.cs SelectShard), and main does not have FenwickTreeBenchmark. Head packs 8 extra cases, so head and base partition differently — shard index 0 does not carry the same class set on the two sides, and here the base side's slice was the heavier one. Any PR that adds a benchmark class can trip this; it is marginal rather than deterministic — #283 (which added IndexedPriorityQueueBenchmark) hit the identical cancellation on shard 4 and merged, while #286 and #288 happened to stay under the line.

Impact is limited to this PR's comparison run. The other five shards passed and aggregate & report (if: always()) succeeded, so the comparison comment is posted from 5/6 shards. The gh-pages dashboard refresh on merge is not affected: the base step is gated on if: github.event_name == 'pull_request', so the post-merge push run on main measures head only (~65 min/shard, comfortably inside the timeout) and publishes complete data — including the new FenwickTree card, whose Array_* baseline pairing was fixed in 20dc83f.

I have deliberately not touched benchmarks.yml here — raising timeout-minutes or reworking the head/base partitioning is a CI design call, and doing it inside a collection PR would mix concerns. Filed as #300.

…s the timeout

The benchmark gate came back cancelled on this PR: `benchmark (shard 0)` hit the
120 min job cap. Its head slice took 65 min and the base slice was cancelled
55 min in; the other five shards ran 86-106 min total and passed.

This is capacity, not a transient. Sharding by case count is the right metric --
BenchmarkDotNet targets a fixed iteration duration and scales invocations to
reach it, so wall time per case is roughly constant regardless of how expensive
one operation is. Adding a class therefore adds time in proportion to its case
count, and at SHARD_TOTAL=6 the heaviest shard had no headroom left: even
removing this PR's 8 cases entirely would have left it at ~119 min against a
120 min cap. main stays green only because push runs skip the base half, which
hid how close the PR path had drifted to the limit.

CiConfig.cs is explicit that the job schedule stays as-is and the matrix is what
scales when the suite grows, so this raises SHARD_TOTAL 6 -> 8 (matrix 0..7)
rather than trading away measurement accuracy. The same total work over 8 slices
puts the heaviest back near ~95 min. The aggregate job already globs
head-shard-*.json / base-shard-*.json and needs the whole matrix, so it picks up
the two extra shards with no change.

Also replaces the TRANSITIONAL note on the base step. It predicted that base runs
would be cancelled until `--shard` landed on main and would then self-heal, but
`--shard` is on main and the base tip honours it -- so the note was stale and
actively misleading: it invites a reader to dismiss exactly this cancellation as
expected. It now says a cancellation there is a real signal.

Verified: the workflow parses, SHARD_TOTAL matches the matrix, and the matrix
enumerates 0..N-1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 25, 2026 04:52

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

@marius-bughiu
marius-bughiu merged commit c850433 into main Jul 25, 2026
17 checks passed
@marius-bughiu
marius-bughiu deleted the feat/issue-289-fenwick-tree branch July 25, 2026 07:58
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.

Add FenwickTree<T> — array-backed Binary Indexed Tree with O(log n) point update & prefix-sum query

2 participants