This document tracks the planned direction for the Celerity project. It is a living document maintained by the project maintainers and updated as priorities shift.
Status legend: planned, in-progress, done, deferred.
- Correctness first. A "fast" collection that returns wrong answers is worthless. Every optimization must be covered by tests.
- Zero-cost abstractions. Hashers are structs, generic constraints are
struct, IHashProvider<T>, so the JIT can devirtualize and inline. This is a hard rule. - Parity with BCL where it makes sense.
TryGetValue,Clear, enumeration,IReadOnlyDictionary<TKey, TValue>— users should be able to drop Celerity in wherever they useDictionary<,>. - Benchmark every perf claim. The README's numbers are the contract. If a change regresses them, it ships only with a written justification.
- Document the tradeoffs. Celerity is not "always faster." Each collection should document the workloads where it wins and where it loses.
Celerity ships three NuGet packages as of v2.0.0 (the package split — see milestone 2.0.0 below; the roadmap previously described a single Celerity.Collections package, which predates that release). Long-term, the project will continue to expand into a family of focused packages — each targeting a specific area where specialized, high-performance implementations can outperform the BCL in niche scenarios. The package structure mirrors the .NET ecosystem's own organization:
Celerity.Collections— dictionaries, sets, and specialized collection typesCelerity.Hashing— hash providers, hash evaluation utilities (positioned on distribution quality, determinism, and zero-cost devirtualization — not on beatingGetHashCode()for speed; see milestone 1.6.0)Celerity.Primitives— low-level utilities that fill genuine BCL gaps:FastMod/FastDiv, struct PRNGs, span varint, integer digit-count, fast/compliant GUID, alignment/bit-packing (see milestone 2.1.0; we deliberately do not reimplement whatBitOperations/TensorPrimitivesalready inline)
Each package will remain narrowly scoped: if a type doesn't offer a measurable performance advantage over its BCL counterpart in at least one documented workload, it doesn't ship.
Alongside the core packages, a separate showcase tier ships standalone libraries built on Celerity, each filling a niche where a pure-managed .NET implementation beats dropping to native code. These are downstream consumers, not core packages: they demonstrate the core in a real problem domain and are held to their own domain bar rather than to the core's "must beat the BCL counterpart" rule (in most cases the BCL has no counterpart at all).
Celerity.Ring— deterministic consistent-hash and rendezvous (HRW) rings for sharding and request routing, producing byte-identical node assignment across OS / architecture / runtime.Celerity.Sentinel— streaming abuse / heavy-hitter detection (top offenders, per-key rate, fan-out cardinality) in a fixed footprint regardless of key cardinality.Celerity.Cardinality— mergeable approximateCOUNT(DISTINCT)and windowed dedup over unbounded managed streams, with deterministic cross-shard merge.
Cross-process sketch wire-serialization is deliberately deferred — it needs core Celerity to expose sketch bytes first, and the core is explicitly not a serialization library.
- Fixed
IntDictionary<TValue>constructor argument forwarding bug. Status:done. - Fixed
IntDictionarykey-0corruption. Status:done. - Fixed
CelerityDictionarydefault(TKey)corruption. Status:done. - Added constructor validation (
capacity,loadFactor). Status:done. - Added
TryGetValue,Clear,Add,TryAddon both dictionaries. Status:done. - Stood up CI workflow (
.github/workflows/ci.yml). Status:done. - Comprehensive test suites: collision tests, load-factor boundary tests, constructor validation. Status:
done. - Added
CONTRIBUTING.md,CHANGELOG.md,ROADMAP.md. Status:done.
The next release rounds out the Celerity.Collections package with missing collection types, expands the hasher library, and stands up CI benchmark tracking.
- Implement
CeleritySet<T, THasher>— set counterpart toCelerityDictionary. Status:done. - Implement
LongDictionary<TValue>—IntDictionaryequivalent forlongkeys. Status:done. - Implement
LongSet<T>—IntSetequivalent forlongvalues; completes the dictionary-to-set parity (IntDictionary→IntSet,CelerityDictionary→CeleritySet,LongDictionary→LongSet). Status:done— shipped in v1.3.0. - Implement
IReadOnlyDictionary<TKey, TValue>onCelerityDictionaryandIntDictionary. Status:done. - Add
Keys/Valuesenumerable views andGetEnumerator()on the dictionaries. Status:done. - Constructor accepting
IEnumerable<KeyValuePair<TKey, TValue>>. Status:done. - Add
GetEnumerator()andIEnumerable<T>conformance on the sets (IntSet,CeleritySet,LongSet). Status:done. - Full
ISet<T>set-algebra conformance on the mutable set family (CeleritySet,SwissSet,IntSet,LongSet) —UnionWith/IntersectWith/ExceptWith/SymmetricExceptWith, theIsSubsetOf/IsSupersetOf/Overlaps/SetEqualsquery family, andCopyTo, all with BCLHashSet<T>semantics, so a Celerity set drops in wherever anISet<T>/ICollection<T>is expected. Status:done— shared once via the internalSetOperationshelper and reconciled against aHashSet<T>oracle by a randomized differential test; completes theHashSet<T>drop-in-parity gap (Guiding Principle #3) discovered while reading the source after the roadmap was otherwise exhausted. Tracked in #240.
- Add
Int32Murmur3Hasher,Int64WangHasher,GuidHasher,UInt32Hasher,UInt64Hasher— alldone. - Add
DefaultHasher<T>fallback toEqualityComparer<T>.Default.GetHashCode(). Status:done.
- Set up
github-action-benchmarkfor continuous performance tracking. Status:done—benchmarksjob inci.ymlruns the full suite on every PR and pushes results togh-pagesonmain, with PR comments and a 200% regression fail-threshold. - Create hash function evaluator for comparing distribution quality. Status:
done—HashQualityEvaluator.Evaluate<T, THasher>(keys, bucketCount)returns aHashQualityReport(collision count / rate, bucket occupancy, max bucket load, chi-squared, and a normalized distribution score) so callers can compare candidate hashers for a given key shape offline. Seedocs/api/hashing.md. Tracked in #2. - Comprehensive benchmark suite: uniform, clustered, and adversarial key distributions. Status:
done—DistributionBenchmarksweeps uniform/sequential/clustered shapes andAdversarialHasherBenchmarkshows the naive hasher degrading to O(n) while Murmur3 recovers. Tracked in #60. - Benchmark suite expansion: realistic workloads, memory-allocation, concurrent-access, cache-locality, large-dataset (millions), and
FrozenDictionary<,>comparison benchmarks. Status:done— added as an extended, on-demand suite kept out of the per-PR CI regression run; seedocs/performance.md. Tracked in #26. - Cross-platform testing (Windows, Linux, macOS). Status:
done. - Improve code coverage. Status:
done— coverage reporting is gated in CI (coverage.yml, 100% line coverage on the library, rendered by an in-repo generator and published to the coverage dashboard), edge-case tests close the non-generic enumerator / throw / backward-shift corners, property-based parity tests (CsCheck) and a seedable differential fuzzer (Celerity.Fuzz, nightly soak) check every collection against its BCL oracle, and the approach is written up indocs/testing.md. Tracked in #29. - Improve documentation. Status:
done— added a performance tuning guide, a BCL migration guide, a troubleshooting guide, and a FAQ, alongside the existing README usage examples, "choosing a collection" table, and API reference. Tracked in #15. - Bump XML doc coverage; treat missing docs as warning-as-error. Status:
done—Celerity.csprojpromotes CS1591 to error.
Focus on raw performance and specialized collection types that serve more advanced use cases.
FrozenCelerityDictionary— build-once, read-many variant with perfect hashing for string keys, comparable in spirit toSystem.Collections.Frozenbut tunable viaIHashProvider<T>. Status:done—FrozenCelerityDictionary<TValue>/<TValue, THasher>search for a collision-free single-probe layout at construction and fall back to linear probing when the chosen hasher collides two keys' raw codes, so lookups are always correct. Tracked in #62.- Frozen collections family — the set counterpart
FrozenCeleritySet/FrozenCeleritySet<THasher>completes the build-once read-many family (FrozenCelerityDictionary→FrozenCeleritySet), sharing the same perfect-hash-with-linear-probing-fallback build and implementingIReadOnlySet<string>. Status:done. Tracked in #22. CelerityMultiMap<TKey, TValue, THasher>— multi-value dictionary. Status:done— a one-to-many map that reusesCelerityDictionary's open-addressed key table and stores aList<TValue?>value group per key;Addappends rather than overwrites,Remove(key, value)/RemoveAll(key)are the two removal shapes, the indexer returns an empty group for an absent key, and the type implementsILookup<TKey, TValue?>. Tracked in #18.CelerityMultiSet<T, THasher>— counting multiset (bag/counter), the element→count sibling that completes theCelerityMultiMapone-to-many family. Status:done— reusesCelerityDictionary's open-addressed table with a parallelint[]multiplicity per element;Add/Add(count)are single-probe increments (vs the two-probeDictionary<T,int>GetValueOrDefaultidiom — the documented BCL-beating frequency-counting workload),Remove/RemoveAll/SetCountmanage multiplicities,Countis distinct elements andTotalCountthe sum of occurrences, and it enumerates(element, count)pairs. Filed and shipped after the roadmap was otherwise exhausted (a tier-(c) enhancement). Tracked in #235.SmallDictionary<TKey, TValue>— flat-array implementation optimized forn <= ~16. Status:done—SmallDictionary<TKey, TValue>linear-scans insertion-dense parallel arrays withEqualityComparer<TKey>.Default(no hasher, so the default key is stored inline rather than out-of-band), tradingO(1)forO(n)to win at smalln; it implementsIReadOnlyDictionary<TKey, TValue?>with the full dictionary surface. Tracked in #61.
- Robin Hood hashing experiment as alternative to linear probing. Status:
done— shipped as a new collection type,RobinHoodDictionary<TKey, TValue, THasher>, a drop-in peer ofCelerityDictionarythat uses Robin Hood open addressing (per-slot probe sequence length, displace-the-richer-resident inserts, backward-shift-with-PSL-decrement deletes) to bound probe-length variance and keep worst-case lookups close to the average on clustered / adversarial keys; negative lookups terminate early via the PSL invariant. The default is unchanged — this is an additional opt-in type for the clustered case, not a replacement (the per-slot PSLintand extra insert work make it a wash or a slight loss on uniform keys). Tracked in #63. - Performance optimizations across existing collections.
- Native AOT support and trimming compatibility. Status:
done— the library is marked<IsAotCompatible>true</IsAotCompatible>(trim + AOT analyzers run on every build), and a Native AOT publish smoke test runs the full collection / hasher surface as a native binary in CI. Seedocs/aot.md. An AOT-vs-JIT benchmark comparison remains a follow-up. Tracked in #32.
A correctness-of-claims pass on the hashing layer, prompted by the observation that the hashers are not necessarily faster than GetHashCode() — and, for int keys, cannot be (int.GetHashCode() is identity, i.e. zero work). The real value of the struct hashers is distribution quality (avalanche), determinism, adversarial resistance, and the zero-cost devirtualized generic — not raw hashing speed. This milestone makes the benchmarks and the docs tell that honest story. It ships in the current single package, before the 2.0.0 restructure.
- Benchmark hashers end-to-end through the dictionaries (insert/lookup across uniform / sequential / clustered / adversarial key distributions), reporting collision rate and avg/max probe length — not just an isolated
Hash()loop. The clustered/adversarial cases are where a strong hasher wins end-to-end even though it "loses" the isolated microbench. Status:done—HasherEndToEndBenchmark(extended suite) times every integer hasher throughIntDictionaryfor insert + lookup across all four key shapes vs the BCLDictionary, and the new publicProbeStatisticsEvaluator/ProbeStatistics(docs/api/hashing.md) replays the real open-addressed linear-probing placement to report average / worst-case probe length and the open-addressing collision rate (surfaced as a deterministic--probe-analysismarkdown report and a measured table indocs/performance.md). The numbers show the cheap hashers winning on uniform/sequential keys and the naive fold collapsing on clustered/adversarial keys while the Wang/Murmur3 finalizers hold near a 1.75 average probe. Tracked in #182. - Make the isolated microbenchmarks honest: consume results (the identity
inthash is otherwise dead-code-eliminated), add anEqualityComparer<T>.Defaultbaseline (the realistic thing a dev replaces), and label identity/GetHashCode()as the zero-work floor no mixing hasher can beat. Status:done— every hasher microbenchmark already XOR-folds its codes into a returned value (BDN consumes it, so no DCE), and bothIntegerHasherBenchmark({Type}_EqualityComparerperint/long/uint/ulong/Guid) andStringHasherBenchmark(EqualityComparer_Default) now carry anEqualityComparer<T>.Default.GetHashCode()baseline arm alongside the directGetHashCode()one — the per-probe call a BCLDictionary<,>actually makes. The class remarks label the microbenchmarks a raw-mixing-cost diagnostic and theint/longidentity/_Bclrows the zero-work floor; the new arms auto-render on the gh-pages Hash function throughput dashboard. Tracked in #183. - Reposition the hasher docs/README away from "faster hashing" toward distribution/avalanche/determinism, with an honest "choosing a hasher" guide (the speed-vs-quality curve, the F14/ahash/FxHash framing, the Marvin32 string-determinism tradeoff, and the caveat that fixed-seed hashers are not a HashDoS defence). Status:
done—README.mdreframes the "up to 2.4× faster" headline as a collection-layout win independent of the hasher, and bothREADME.mdanddocs/api/hashing.mdlead with distribution/determinism and carry an explicit HashDoS caveat (fixed-seed hashers are not a flooding defence; what stops flooding is a keyed PRF with a secret, per-process-random key, so BCL Marvin32 is the safe default for untrusted string keys).hashing.md's "Choosing a hasher" section gains a speed-vs-quality-curve framing block, and the benchmark docs acrosshashing.md/docs/performance.mdreframe the isolatedHash()sweeps as a raw-mixing-cost diagnostic. Tracked in #184. - Add explicit identity/passthrough integer hashers (
Int32IdentityHasher/Int64IdentityHasher) as the zero-work floor, and document the rule: uniform/trusted keys → skip mixing; clustered/adversarial keys → mix. Status:done—Int32IdentityHasher(Hash(key) => key) andInt64IdentityHasher(Hash(key) => (int)key) ship as the labelled floor of the integer hasher ladder (no mixing hasher beats identity on speed; the value of the struct hashers is distribution/determinism, not hashing speed), are exercised as*_Identityrows inIntegerHasherBenchmark, and carry the skip-vs-mix decision rule plus the open-addressed-table-sensitivity and not-a-HashDoS-defence caveats indocs/api/hashing.mdand the README. Library defaults are unchanged (identity is opt-in). Tracked in #185.
Split the monolithic Celerity.Collections into focused packages mirroring the .NET package structure. This is a breaking change in packaging (not necessarily in API). The new collections and infrastructure work below has shipped, the package restructure itself — the defining work of this milestone — has landed (the library builds and packs as three packages, each multi-targeting net8.0;net9.0;net10.0, #189), and the release pipeline is now complete (symbol packages, SourceLink, deterministic builds, and a publish-time package-validation gate, #190). v2.0.0 shipped on 2026-06-21, closing the human-gated release review (#213); the milestone is complete and closed.
Shipped: three projects under src/ form an acyclic layer — Celerity.Primitives (FastUtils, struct PRNGs, VarInt, FastGuid) ← Celerity.Hashing (IHashProvider<T>, the hashers, the evaluators) ← Celerity.Collections (the Celerity assembly: dictionaries, sets, frozen/sketch types). Namespaces are unchanged except FastUtils, which moved from Celerity to Celerity.Primitives (#187). dotnet pack produces three .nupkgs with the correct transitive dependency graph and shared MinVer versioning.
Celerity.Collections— dictionaries, sets, and specialized collections.Celerity.Hashing—IHashProvider<T>, built-in hashers,HashQualityEvaluator. Status:done— extracted intosrc/Celerity.Hashing(depends onCelerity.PrimitivesforFastUtils.NextPowerOfTwo), packs independently, AOT analyzers + CS1591-as-error preserved. Tracked in #186.Celerity.Primitives— low-level utilities, seeded withFastUtils; content expansion is milestone 2.1.0. Status:done— extracted intosrc/Celerity.Primitiveswith no package dependencies;FastUtilsmoved to theCelerity.Primitivesnamespace. Tracked in #187.- Preserve back-compat for existing
Celerity.Collectionsconsumers (meta-package and/or[TypeForwardedTo]). Status:done—Celerity.Collectionscarries non-private NuGet dependencies on the two lower packages (source/meta-package back-compat) and a full[TypeForwardedTo]set inTypeForwarders.csfor every moved type (binary back-compat); migration written up indocs/migration.md. Tracked in #188. - CI: build, pack, and publish three packages with shared MinVer versioning. Status:
done— per-package metadata (id, description, tags, icon, README) and shared MinVer lockstep were already in place; this work completes the pipeline. Shared publishing settings now live once insrc/Directory.Build.props: every shipped package emits a.snupkgsymbol package (IncludeSymbols+SymbolPackageFormat=snupkg, portable PDBs), embeds SourceLink (the .NET 8+ SDK's built-in GitHub SourceLink — no explicit package ref — plusPublishRepositoryUrl/EmbedUntrackedSources, so the.nupkgcarries the repo URL + commit SHA and the PDB maps every source file toraw.githubusercontent.com/<commit>/…), and is built deterministically (ContinuousIntegrationBuild=truein CI, normalizing embedded source paths).release.yml/nightly-preview.ymlpack with-p:ContinuousIntegrationBuild=true, upload and attach the.snupkgs (thedotnet nuget push *.nupkgloop auto-pushes the adjacent symbol package to the NuGet.org symbol server), and run a publish-gate validator (.github/scripts/validate-packages.ps1) that fails the release unless exactly the three expected packages were produced, each with a matching.snupkgand the required license / README / icon / repository-URL-with-commit metadata. Tracked in #190.
- Specialized collections for domain-specific workloads (e.g. graph traversal, spatial indexing). Status:
done— four specialized types shipped and #30 was closed as substantially complete (the two remaining checklist entries,StringDictionaryandStructDictionary, were descoped as redundant withCelerityDictionary's existing string-hasher surface and zero-boxing struct-key support — per the guiding rule, a type that doesn't beat the BCL on a documented workload doesn't ship).BloomFilter<T, THasher>is a probabilistic membership filter with bit-array storage, no false negatives, and a tunable false-positive rate, sizingm/kfrom the expected element count and deriving itskbit probes from a singleIHashProvider<T>call by double hashing.BitSetis its exact, deterministic counterpart: a dense fixed-length bit vector packed into 64-bit words withO(n/64)hardware-popcount cardinality (Count) and SIMD-accelerated bulkAnd/Or/Xor/Not, a faster, count-aware alternative toSystem.Collections.BitArray.HyperLogLog<T, THasher>is the probabilistic cardinality estimator: it counts the number of distinct elements in a stream of any size from a fixed array of2^precisionone-byte registers (16 KB by default) with a ~0.8% relative standard error and no growth with the data, derives its 64-bit hash from a singleIHashProvider<T>call by SplitMix64 avalanche, applies linear counting for small cardinalities, and merges equal-precision estimators withUnionWithfor distributed counting.CountMinSketch<T, THasher>completes the streaming-sketch trio (membership → cardinality → frequency): it estimates how many times each element occurs from a fixeddepth × widthgrid of counters sized from anepsilon/deltaerror budget, never underestimates (overestimates bounded byepsilon · TotalCountwith confidence1 − delta), derives itsdepthcounter columns from a singleIHashProvider<T>call by double hashing, and merges equally-sized sketches withUnionWithfor distributed heavy-hitter / frequency counting. Tracked in #30 (closed).CuckooFilter<T, THasher>later extended the family with deletable membership (#223): the same no-false-negatives / tunable-false-positive contract asBloomFilterbut backed by partial-key cuckoo hashing (power-of-two fingerprint buckets,i2 = i1 XOR h(fingerprint), eviction with a single-entry victim cache), so it supportsRemovewith ≤2-bucket lookups — the membership filter for a set that shrinks as well as grows. Status:done.TopKSketch<T, THasher>later completed the streaming-sketch family's fourth axis — top-k / heavy hitters (#238): the Space-Saving algorithm (Metwally et al. 2005) reports a high-cardinality stream's most frequent elements from a fixedkmonitors (an indexed min-heap keyed on count, with the element→monitor index dogfoodingCelerityDictionary), inO(k)memory rather than theO(distinct)aDictionary<T,int>frequency table needs to rank the top few; it never underestimates a monitored count and never misses an element aboveTotalCount / k. Add-and-query only, with noUnionWith(bounded top-k summaries have no exact merge). Filed and shipped after the roadmap was otherwise exhausted (a tier-(c) enhancement). Status:done. - Memory-pooled collections for zero-allocation hot paths. Status:
done—PooledCelerityDictionary<TKey, TValue, THasher>is a drop-in,IDisposablepeer ofCelerityDictionarywhose backing key/value arrays are rented fromArrayPool<T>.Sharedand returned onDispose(and on every internal resize), recycling buffers across build/use/dispose cycles to cut Gen 0 / LOH pressure on hot paths that rebuild dictionaries frequently. It tracks its logical power-of-two capacity independently of the (possibly over-provisioned) rented array length, clears reference-type buffers on return to prevent leaks, and throwsObjectDisposedExceptionafter disposal. Tracked in #21. - SIMD-accelerated probing (SSE2/AVX2) similar to Swiss Tables /
F14. Status:done— shipped as a new opt-in collection type,SwissDictionary<TKey, TValue, THasher>, a drop-in peer ofCelerityDictionarythat keeps a parallel one-byte control-tag array so a single portableVector128compare tests a whole 16-slot group per probe, filtering candidates by a 7-bit hash fragment before any key comparison; deletion uses tombstones reclaimed by an occasional rehash. The default is unchanged — this is an additional type for lookup-heavy workloads (large tables, many negative lookups, clustered keys), at the cost of one control byte per slot. Tracked in #64. - Struct-of-arrays layout experiment for cache-friendly memory access. Status:
done— shipped as a new opt-in collection type,HashCachingDictionary<TKey, TValue, THasher>, a drop-in peer ofCelerityDictionarythat keeps a dense side array of 32-bit hash fingerprints alongside the parallel key/value arrays. A probe scans only that compact metadata buffer and dereferences a key (running the full equality check) only on a fingerprint match, so cache-cold lookups and lookups with expensive key equality short-circuit on a single integer compare; because the forced occupied bit sits above the table mask, the cached fingerprint also yields the slot index directly, so a resize re-homes every entry without recomputing a single hash. The default is unchanged — this is an additional type for lookup-dominated / costly-equality workloads, complementary to the SIMD-probingSwissDictionary(#64), at the cost of four bytes of metadata per slot. Tracked in #65.
- Multi-target
net8.0;net9.0(evaluatenet10.0) across all three packages, so newer-runtime consumers get TFM-gated optimizations (AVX-512 SIMD paths, JIT improvements). Status:done— all three packages now multi-targetnet8.0;net9.0;net10.0(the "evaluate net10.0" decision: included now, since net10.0 is GA/LTS and the SDK the family already builds with; net9.0 kept as required though it is STS; net8.0 LTS stays the floor).dotnet packemitslib/net8.0+lib/net9.0+lib/net10.0in each.nupkgwith the per-TFM transitive dependency graph intact, the shared TFM list lives once insrc/Directory.Build.props, and CI provisions all three SDKs sodotnet testruns the suite per-TFM and theaot-publishjob matrixes the Native AOT smoke test over every framework. No#if-gated code paths today (the source compiles identically on every TFM); multi-targeting is the enabling step for later runtime-gated optimizations, andCONTRIBUTING.mdrecords the#if NET9_0_OR_GREATER/NET10_0_OR_GREATER+ net8.0-fallback convention. Tracked in #189. - Publish a results dashboard so users can track performance over time. Status:
done— the core per-commit dashboard and the weekly extended dashboard are published togh-pagesand linked from the site nav.
The "fast-utils" expansion that fills Celerity.Primitives with specialized BCL alternatives. Comprehensive research against the current .NET (8/9/10) surface shows the BCL has closed most classic gaps — System.Numerics.BitOperations, System.Numerics.Tensors.TensorPrimitives, Convert.ToHexString, System.Buffers.Text.Base64, and generic-math Math already inline to optimal/SIMD code. So this milestone ships only the defensible white space, each with a documented BCL-beating workload (the hard rule), and deliberately does not reinvent what the BCL already does well.
FastMod/FastDiv— Lemire reciprocal modulo & division by a runtime-constant divisor (the BCL'sHashHelpers.FastModisinternal-only); 2–4× over%//for repeated mod by the same divisor (hash buckets, ring buffers, sharding). Status:done— shipped onFastUtilswith 32-bit (uint→ulongmultiplier) and 64-bit (ulong→UInt128multiplier) overloads:GetFastModMultiplierprecomputes theceil(2^W / d)reciprocal once, thenFastMod/FastDivreduce each operation to a widening multiply + shift.FastModis exact for every value anddivisor >= 1;FastDivfordivisor >= 2(thedivisor == 1multiplier overflows to 0 — a documented call-site guard). Correctness is fuzzed against%//across both widths (representative + extreme divisors, boundary + random + exhaustive-low-range dividends), benchmarked vs the hardware operators in the extended-suiteFastModBenchmark, documented indocs/api/utilities.mdand the README, and exercised by the Native AOT smoke test. Tracked in #191.- Struct PRNG suite — value-type, allocation-free, inlinable, seed-deterministic xoshiro256** / xoroshiro128+ / SplitMix64 (+wyrand/PCG).
System.Randomis a heap class behind virtual dispatch and its seeded path falls back to the legacy Knuth algorithm. Curated, no marginal variants. Status:done—Celerity.Primitives.SplitMix64/Xoshiro256StarStar/Xoroshiro128Plus/WyRand/Pcg32ship as mutablestructs implementing a one-methodIRandomSource(ulong NextUInt64()); the sharedNextUInt32/NextDouble/NextSingle/NextBool/ bounded-and-unbiased (Lemire)NextInt/NextInt64/NextBytessurface is built once over the interface asref thisextension methods constrained towhere TRng : struct, IRandomSource, so it devirtualizes, inlines, and runs zero-cost over any generator (e.g. a generic Fisher–Yates shuffle). Every constructor is explicitly seeded and deterministic; the multi-word generators expand the seed throughSplitMix64so every seed (including0) is valid. Cross-checked against independent reimplementations and the published SplitMix64 seed-0 vector, covered family-wide byRandomSourceContractTests, benchmarked vs seeded/sharedSystem.Randomin the extended-suitePrngBenchmark, documented indocs/api/utilities.mdand the README, and exercised by the Native AOT smoke test. Tracked in #192. - Span-based varint codec — LEB128 + zig-zag
Try(Write|Read)over spans (the BCL's 7-bit-encoded int is only onBinaryReader/BinaryWriter, stream-bound and allocating). Status:done—Celerity.Primitives.VarIntshipsTryWriteVarInt/TryReadVarIntoverSpan<byte>/ReadOnlySpan<byte>foruint/ulong(LEB128) andint/long(zig-zag + LEB128), plus aVarIntLengthsize helper, theMaxVarIntLength32/MaxVarIntLength64buffer-sizing ceilings, and standaloneZigZagEncode/ZigZagDecodetransforms. EveryTry*is bounds-safe (returnsfalsewith0bytes on a short / truncated / over-length / overflowing buffer, never throws). Round-trip-fuzzed (per-width boundaries + extremes + dense exhaustive low-range sweeps), benchmarked vsBinaryWriter.Write7BitEncodedInt64in the extended-suiteVarIntBenchmark, documented indocs/api/utilities.mdand the README, and exercised by the Native AOT smoke test. Tracked in #193. - Integer digit-count /
Log10— publicCountDigits(the BCL's LZCNT-based one isinternal); for buffer sizing and column alignment. Status:done—FastUtils.CountDigitsshipsuint/ulong(exact, branch-lean: the 32-bit path is Lemire's single-Log2/LZCNT-plus-magic-table count, the 64-bit path a one-division comparison ladder) plus signedint/longoverloads that count the magnitude (sign excluded,MinValuehandled without overflow), and the companion integerLog10(uint)/Log10(ulong)(CountDigits - 1, exact at every power of ten where the floating-pointMath.Log10mis-rounds;Log10(0)returns0). Correctness is reconciled againstvalue.ToString().Length(two dense exhaustive[0, 2,000,000)sweeps + every power-of-ten boundary + ~200k random per width), benchmarked vs a naive divide-by-ten loop and(int)Math.Log10 + 1in the extended-suiteCountDigitsBenchmark, documented indocs/api/utilities.mdand the README, and exercised by the Native AOT smoke test. Tracked in #194. - Fast non-crypto GUID v4 (from the struct PRNG) + RFC-9562 big-endian v7 (sortable, DB-friendly; the BCL's
CreateVersion7uses a non-big-endian layout that bloats DB indexes). Status:done—FastGuid.CreateVersion4<TRng>(ref TRng)is a non-cryptographic random v4 filled from anyIRandomSourcestruct PRNG, andFastGuid.CreateVersion7<TRng>(ref TRng, long unixTimeMilliseconds)is an RFC 9562 v7 whose 48-bit timestamp sits in the big-endian most-significant bytes so the canonical string sorts in creation order (unlike .NET 9's mixed-endianGuid.CreateVersion7, which scrambles the DB sort order); theGuidis built via the field constructor with big-endian reads so this needs no .NET 9-only API (the library targets net8.0).GuidV7Generator<TRng>adds a strictly monotonic v7 sequence (RFC 9562 monotonic-counter method: a 12-bitrand_acounter that advances within a millisecond and borrows from the next on overflow), so a same-millisecond burst is still strictly increasing. Both set the correct version/variant bits, are deterministic from a seeded generator, and are documented prominently as NOT cryptographically secure (useGuid.NewGuid()for unguessable IDs). Benchmarked vsGuid.NewGuid()(andGuid.CreateVersion7under#if NET9_0_OR_GREATER) in the extended-suiteGuidBenchmark, documented indocs/api/utilities.mdand the README, and exercised by the Native AOT smoke test. Tracked in #195. - Alignment helpers + span bit-packing over caller-owned memory (
AlignUp/AlignDown/IsAligned, span bit get/set/scan/popcount), distinct from the owningBitSetcollection. Status:done—FastUtils.AlignUp/AlignDown/IsAlignedship power-of-two alignment forint/longsizes and pointer-sizednuintaddresses (theinternalBCLAligntrick, exposed andBitOperations.IsPow2-validated), andCelerity.Primitives.SpanBitsis the non-owning counterpart toBitSet:Get/Set/Clear/Flip/ hardware-POPCNTPopCount/TZCNTNextSetBitscan over a caller-ownedSpan<ulong>(astackallocbuffer, a slice, a pooled array), plus aWordCountsizing helper — whereBitSetowns its storage,SpanBitsoperates on memory you already manage. Reconciled against a modulo oracle (alignment) and abool[]model (bits), benchmarked vsSystem.Collections.BitArrayin the extended-suiteSpanBitsBenchmark, documented indocs/api/utilities.mdand the README, and exercised by the Native AOT smoke test. Tracked in #196. - Sequential bit-field cursors over caller-owned spans —
BitWriter/BitReader. Status:done— a bounds-safe pair ofref structcursors for packing and unpacking arbitrary-width bit fields over aSpan<byte>/ReadOnlySpan<byte>, with no stream and no allocation, filling the gap between byte-granularVarIntand random-accessSpanBits: a record of odd-width fields occupies exactlyceil(total_bits / 8)bytes instead of one byte per field (wire protocols, compression bitstreams, packed columnar / bitmap-index encodings). Bit order is LSB-first (the DEFLATE convention); the BCL has no span-based multi-bit bit writer. Released in v2.3.0 and rostered here retroactively by the 2026-Q3 roadmap review — it landed after this milestone was otherwise complete, but belongs to theSpanBits/VarIntline above rather than to the collection work it shipped alongside.
- Fused/specialized SIMD reductions not covered by
TensorPrimitives(simultaneous min+max, integer histogram, overflow-checked sum) — spike, ship only the winners. Status:done— shipped asCelerity.Primitives.SimdReductionswith the two candidates that beat the BCL composition on a documented workload:MinMax(int/long/uint/ulong) folds two runningVector<T>accumulators in a single pass vs the two-passTensorPrimitives.Min+TensorPrimitives.Max, measuring ~1.8× faster on a large out-of-cache span (a memory-bandwidth win; a wash in-cache, documented), andCheckedSum(int) widens each lane tolongso the SIMD accumulation cannot overflow and throwsOverflowExceptionrather than wrapping likeTensorPrimitives.Sum, measuring ~4.6× faster than the only safe alternative (a scalarcheckedloop). The third candidate, an integer histogram / bincount, was evaluated and not shipped — its only BCL alternative is LINQGroupBy().Count(), the win is purely allocation avoidance achievable with a one-linecounts[v]++loop, and the scatter pattern does not vectorize portably. Seedocs/api/utilities.md. Tracked in #197. - Guaranteed-branchless conditional
Select— verify the JIT actually branches (it already emitscmovforMath.Min/Max/Abs/Clamp) before shipping. Status:done— the spike confirmed the JIT does not reliably if-convert a general data-dependentcondition ? a : b: a per-element blend over a 1,000,000-element span with a 50/50 unpredictable condition runs ~6× faster branch-free (~0.5 ms vs ~3.0 ms), the textbook misprediction signature. Shipped asCelerity.Primitives.Branchless.Select— scalar overloads forint/long/uint/ulong/float/double(floats bit-exact via integer-bit reinterpret) plus bulk per-element span blends (int/long/float/double) that auto-vectorize, all via the mask trickwhenFalse ^ ((whenTrue ^ whenFalse) & mask). The recognisedcmovidioms (Math.Min/Max/Abs/Clamp) are deliberately not re-shipped, and the docs/benchmark flag that the win is specific to the unpredictable-condition case (a well-predicted branch is free). Seedocs/api/utilities.md. Tracked in #198.
Per the guiding rule, these are not worth shipping because they already inline to optimal/SIMD code: next-power-of-two / IsPow2 / Log2 / PopCount / LeadingZeroCount / TrailingZeroCount / RotateLeft/Right (System.Numerics.BitOperations); SIMD Sum/Min/Max/Dot/IndexOf/Contains (TensorPrimitives, generic over INumber<T>, + MemoryExtensions/SearchValues); hex and Base64 encode/decode (Convert.ToHexString, System.Buffers.Text.Base64, AVX-512); byte-swap/endianness (BinaryPrimitives); branchless Min/Max/Abs/Clamp (JIT cmov); and generic xxHash/CRC span hashing (System.IO.Hashing — depend on it rather than reimplement).
Rostered retroactively by the 2026-Q3 roadmap review. The planned roadmap was complete through 2.1.0, but development did not stop — it continued under the convention this project had already been following informally: when the plan is exhausted, the next item comes from reading the source, either a BCL-parity gap or a family whose members are not symmetric. That produced a full release worth of collections that were never on the roadmap. This section records them so the roadmap stays a faithful account of what shipped, not only of what was planned.
The dictionary family had four specialized performance peers while the set family had none. Closing that asymmetry produced four types, each a drop-in peer of CeleritySet differing only in probing/storage strategy. All done.
RobinHoodSet<T, THasher>— Robin Hood open addressing; bounds probe-length variance on clustered / adversarial elements, negative lookups exit early.HashCachingSet<T, THasher>— parallel cached hash fingerprints; a probe compares one integer before dereferencing an element, winning on lookup-dominated sets and costly-equality elements.PooledCeleritySet<T, THasher>—ArrayPool-backed andIDisposable; recycles buffers for short-lived, frequently-rebuilt sets instead of generating Gen 0 / LOH garbage.SmallSet<T>— flat-array linear scan forn <= ~16, the set counterpart ofSmallDictionaryand the last dictionary→set parity gap.
Their differential fuzz targets landed with them (#252), closing a harness gap where the newest sets were not driven against a BCL oracle.
EnumSet<TEnum>— bit-vector set for enum element types (the .NET analogue of Java'sEnumSet); membership is a single bit test and set algebra is word-wise. Status:done. Tracked in #259.EnumMap<TEnum, TValue>— dense array-backed dictionary for enum keys, the dictionary counterpart ofEnumSet; a lookup is a direct array index, not a hash probe. Status:done. Tracked in #263.
Both support enums whose members are small non-negative integers; a negative or sparse ([Flags]) enum throws at construction. Note: these are enum-generic, so they cannot join the int-parameterized shared test suites — they carry dedicated coverage instead.
Each of these fills a genuine hole in the framework rather than beating an existing BCL type.
Deque<T>— growable double-ended queue over a circular buffer. .NET has no array-backed deque (Queue<T>/Stack<T>are single-ended;LinkedList<T>allocates per element). All four end operations plus a front-relative indexer areO(1)amortized. Status:done. Tracked in #268.DisjointSet<T>— union-find with union-by-size and path halving. .NET ships no union-find; the idiomaticDictionary<T, HashSet<T>>merge is quadratic where this is near-linear. Status:done. Tracked in #272.LruCache<TKey, TValue, THasher>— fixed-capacity LRU. .NET ships no bounded LRU cache; the steady-state get/put/evict path allocates nothing, unlike the idiomaticDictionary+LinkedListhand-roll. Status:done. Tracked in #266.IndexedPriorityQueue<TElement, TPriority, THasher>— an addressable binary heap: unlikePriorityQueue<,>it supports decrease-key / update-priority and remove-by-element inO(log n), the operation Dijkstra / A* / event simulation need and the BCL type cannot do without a lazy-deletion workaround. Status:done.
XorFilter<T, THasher>— build-once, immutable membership filter; smaller (~9.84 bits/element) and faster to query (three probes, no probe loop) thanBloomFilterorCuckooFilterat the same false-positive rate, completing the membership-filter family with its static member. Status:done.
BitWriter/BitReaderalso shipped in this release; they are rostered under milestone 2.1.0 above, alongside theSpanBits/VarIntwork they extend.
The planned roadmap is complete through 2.1.0, and the two releases since have been driven entirely by source-reading rather than by the plan. Rather than let that work keep landing unrostered — the drift the 2026-Q3 review was opened to catch — 2.4.0 is a standing milestone that new post-roadmap work is filed against as it is identified. It is not a fixed scope with a completion date; it is the home for the tier-(c) lane.
Work admitted here is held to exactly the same bar as everything above: the hard rule (a documented workload where it beats its BCL counterpart, or a genuine BCL gap) and the non-goals.
Shipped to main, awaiting the next release tag:
Trie<TValue>— ordered prefix tree mappingstringkeys to values; the operation no hash table can do is prefix enumeration in sorted order without scanning every key. Status:done. Tracked in #285.SparseSet— bounded-universe[0, Universe)integer set withO(1)clear and dense iteration; the classic ECS / graph-visited-set structure, whereHashSet<int>.Clear()and hash iteration both lose. Status:done. Tracked in #287.FenwickTree<T>— Binary Indexed Tree over a fixed-length sequence:O(log n)point update and prefix-sum query, where a running-sum array givesO(1)query butO(n)update and a plain array gives the reverse. Status:done. Tracked in #289.
The 2026-Q3 review surveyed the shipped surface against the current .NET 8/9/10 BCL and adversarially verified each candidate against the hard rule and the non-goals. Thirty-six candidates were proposed; twelve were rostered, in four themes. The open issues on the 2.4.0 milestone carry the detail.
Drop-in parity and correctness in the shipped surface. Work on code already on NuGet, and the highest-confidence group.
- Fix
HyperLogLog's hash-entropy floor.Hash64widens a 32-bitIHashProvider<T>result, so the reachable hash space is 2^32 — while the type's own docs assert a 64-bit space and skip the classical large-range correction on that basis. The bias exceeds the advertised 0.81% standard error from ~1e8 distinct elements, in exactly the regime the type is sold for. Needs anIHashProvider64<T>sibling interface and 64-bit-native hashers; the same floor is inherited by every other sketch. Status:planned. - Implement
IReadOnlySet<T>on the mutable sets andIDictionary<TKey, TValue>on the dictionaries. The sets implementISet<T>and the dictionariesIReadOnlyDictionary<,>, butISet<T>does not derive fromIReadOnlySet<T>— so an ordinary BCL-shaped API taking either interface is a compile error against a Celerity type today. This is the same Guiding Principle #3 gap the 2.2.0 set-algebra work closed, one level up. Status:planned. - Delete the per-probe virtual call. The probe loops test for an empty slot with
EqualityComparer<TKey>.Default.Equals(slot, default(TKey)), which the JIT devirtualizes for value-type keys but not under__Canon-shared reference-type instantiations — onecallvirtper probe iteration to perform what is a null check. Guiding Principle #2 exists to remove exactly this. Status:planned. - Span-keyed lookups on the string-keyed collections. .NET 9's
GetAlternateLookup<ReadOnlySpan<char>>lets the BCLDictionaryprobe with a span key and no allocation; Celerity's string-keyed types require a materializedstring, so the BCL is now ahead on the axis this library has invested most in. Status:planned.
The ordered / compressed integer-data lane. Opened by the sorted-container hole — 38 collections and not one sorted map or set, with Trie the only ordered type, and that one string-keyed. The B-trees below close that half; the compressed-integer half is still open.
BTreeDictionary<TKey, TValue, TComparer>/BTreeSet<T, TComparer>— cache-friendly sorted containers againstSortedDictionary<,>/SortedSet<T>, which are red-black trees with a pointer chase and an allocation per node. Status:done. Tracked in #305.CompressedIntSet— a Roaring-style compressed set of 32-bit integers, covering the huge-and-sparse shape that neitherBitSet(dense, bounded) norSparseSet(small universe,O(Universe)memory) norIntSet(hash) serves. Status:planned.RankSelectBitVector— succinctRank/Selectover a dense bit vector, the primitive the above compose on. Status:planned.- Sorted-span set algebra in
Celerity.Primitives— merge-basedIntersect/Union/Except/IntersectCountover already-sorted spans, where the BCL answer is LINQ or aHashSetround-trip. Status:planned.
A fourth core package: Celerity.Sorting. Array.Sort / MemoryExtensions.Sort are scalar comparison introsort with no radix, counting, or selection path for primitive keys — and the BCL structurally cannot close it, because Array.Sort is contractually in-place while radix needs O(n) scratch. That is precisely the flexibility-for-speed trade this project's Vision licenses, against a named BCL counterpart. Would layer on Celerity.Primitives, mirroring how Hashing and Collections layer today. Status: planned — see the package-scoping caveat below.
Build- and release-pipeline integrity. Three guards the repo advertises but does not have.
- The coverage gate measures one of the six shipping assemblies.
src/coverage.runsettingsfilters to[Celerity]*with the comment "Measure only the shipping library assembly" — written when there was one.Celerity.Hashing,Celerity.Primitivesand the three showcase packages are unmeasured, whileCONTRIBUTING.mdandCLAUDE.mddescribe the 95%/90% gate as library-wide. Status:planned. - Nothing can fail after the NuGet push.
release.ymlpushes six packages irreversibly, then extracts the release notes and creates the GitHub Release — so an over-long release body (a failure this repo has actually hit) leaves a half-published release. The notes check should be hoisted ahead of the push. Status:planned. - No API-compatibility gate. Six packages publish on a tag with no
ApiCompat/PackageValidation/ public-API-baseline check anywhere in the repo — in a project that already needed a hand-writtenTypeForwarders.csto survive one assembly split. Status:planned.
Two areas were judged real but deliberately deferred rather than rostered: a Celerity.Statistics package (DDSketch / reservoir sampling / running moments — a coherent fourth axis, but two new packages in one cycle is too much at once), and a batch of fuzz-target and AOT-smoke-coverage gaps (real, but low expected defect yield; better folded into whichever collection PR lands next than pursued on their own).
- We are not trying to replace
Dictionary<,>in every scenario. Celerity trades flexibility for speed on specific shapes; that tradeoff must be documented, not hidden. - We are not a thread-safe collections library. Callers that need concurrency should compose with locks or use
ConcurrentDictionary<,>. - We are not a serialization library. Celerity collections should be straightforward to serialize via System.Text.Json / MessagePack, but we won't ship formatters ourselves.
- We are not a general-purpose data structures library. If a collection doesn't beat the BCL on a documented benchmark, it doesn't belong here.