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 currently ships a single NuGet package (Celerity.Collections). Long-term, the project will 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 will mirror 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.
- 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). What remains is the human-gated go/no-go on the actual v2.0.0 tag (#213).
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.
- 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).
- 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.