You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Merge remote-tracking branch 'origin/main' into fix/issue-315-release-gates
Conflicts were all additive-vs-additive, kept both sides:
- .github/workflows/ci.yml — main's `dashboard-coverage` job (#301) and this
branch's `release-gates` job landed in the same slot after `build-and-test`.
- CHANGELOG.md — main's two dashboard entries and this branch's two release-gate
entries both appended to [Unreleased] > Fixed.
- ROADMAP.md — the pipeline-integrity list: three bullets flipped to `done` here,
a fourth (the dashboard guard) added and flipped on main.
Package validation still passes against the 2.4.0 baseline: #311's new surface
(ISpanHashProvider, the span-keyed overloads, StringInternTable) is purely
additive, so the gate this branch introduces stays green on the merged tree.
Copy file name to clipboardExpand all lines: CHANGELOG.md
+8Lines changed: 8 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,12 @@ All notable changes to Celerity are documented here. This project follows [Keep
6
6
7
7
### Added
8
8
9
+
-**`ISpanHashProvider`** in `Celerity.Hashing` — a `Hash(ReadOnlySpan<char>)` contract returning exactly what `Hash(string)` returns for the same characters. All 23 built-in `String*Hasher` types implement it. Closes [#311](https://github.com/marius-bughiu/Celerity/issues/311).
10
+
-**Span-keyed lookups** — `TryGetValue` / `ContainsKey` / `Contains` now take a `ReadOnlySpan<char>` on `FrozenCelerityDictionary`, `FrozenCeleritySet`, `CelerityDictionary<string, …>`, `CeleritySet<string, …>` and `Trie<TValue>`, so a caller holding a slice of a buffer no longer allocates a `string` per probe. Results match the `string` overloads; an empty span means `""`, never the `null` key. Additive on all three target frameworks. Closes [#311](https://github.com/marius-bughiu/Celerity/issues/311).
11
+
-**`StringInternTable` / `StringInternTable<THasher>`** in `Celerity.Collections` — canonicalizes tokens probed as spans: `GetOrAdd` returns the shared `string` and allocates only on a miss, so a parse over few distinct tokens allocates per token rather than per occurrence. Implements `IReadOnlyCollection<string>`; not thread-safe. Closes [#311](https://github.com/marius-bughiu/Celerity/issues/311).
12
+
- Test coverage for the above: `SpanHashParityTests` (the `Hash(s) == Hash(s.AsSpan())` contract, per hasher), `SpanLookupTests`, `StringInternTableTests`, `StringInternTableDifferentialTests`, a `Celerity.Fuzz` target, and Native AOT smoke-test coverage. Closes [#311](https://github.com/marius-bughiu/Celerity/issues/311).
13
+
-`StringInternTableBenchmark` in the CI-tracked suite, plus `SpanLookup` rows on the frozen-dictionary and trie benchmarks, with the matching dashboard cards. Closes [#311](https://github.com/marius-bughiu/Celerity/issues/311).
14
+
- Docs for the new surface: `ISpanHashProvider`, span-keyed lookups and `StringInternTable` in the API reference, and matching README entries. Closes [#311](https://github.com/marius-bughiu/Celerity/issues/311).
9
15
-**`StringKeyProbeBenchmark`** — the tracked suite's first `string`-keyed dictionary and set rows (lookup hit, lookup miss, set `Contains`), shown as the **String-keyed probe** card on the benchmark dashboard. Closes [#308](https://github.com/marius-bughiu/Celerity/issues/308).
10
16
-**`ReferenceKeyProbeTests`** — cross-collection coverage for vacant-slot detection on the open-addressed collections with reference-type keys. Closes [#308](https://github.com/marius-bughiu/Celerity/issues/308).
11
17
-**`BTreeDictionary<TKey, TValue, TComparer>` and `BTreeSet<T, TComparer>`** (with `BTreeDictionary<TKey, TValue>` / `BTreeSet<T>` aliases and the `DefaultComparer<T>` struct comparer) in `Celerity.Collections` — the library's first sorted map and set, and the B-tree the BCL lacks. Up to 31 keys per node keep a lookup `log₃₂(n)` node visits deep instead of chasing the `log₂(n)` pointers a red-black tree costs, and both add the ordered surface a hash table cannot answer: `Min` / `Max`, lower / upper bound, `EnumerateRange` in `O(log n + k)`, and in-order enumeration. They win on the interleaved insert + lookup + range-scan workload and on memory, and lose slightly on a delete-dominated one. Not thread-safe. Closes [#305](https://github.com/marius-bughiu/Celerity/issues/305).
@@ -19,6 +25,8 @@ All notable changes to Celerity are documented here. This project follows [Keep
19
25
20
26
-**A breaking API change could ship silently.**`dotnet pack` now validates every package against its last published version across all three TFMs and fails on any break, so a removed or narrowed public member can no longer reach NuGet.org with CI green. It runs on every PR, not just at release, and intentional breaks are recorded in a reviewed suppression file. CI-only; no consumer-visible behaviour change. Closes [#315](https://github.com/marius-bughiu/Celerity/issues/315).
21
27
-**A bad `CHANGELOG.md` could half-publish a release.** The release-notes check ran after the irreversible NuGet push, so a missing section — or one over GitHub's ~125k release-body cap, which this repo has overrun before — left six packages published and no release. It now runs before anything is pushed, with a body-size assertion added, and the validated notes are handed to the release step verbatim. Closes [#315](https://github.com/marius-bughiu/Celerity/issues/315).
28
+
-**The `EnumMap` and `EnumSet` cards on the benchmark dashboard rendered empty.** The page required an `(ItemCount: N)` suffix on every result name, and both benchmarks deliberately declare no item-count sweep, so their measurements were published and then discarded at render time. Both cards now chart their real numbers, and unparameterized benchmarks are excluded from the headline speedup stats. Closes [#301](https://github.com/marius-bughiu/Celerity/issues/301).
29
+
- A blank dashboard card is now a red CI check rather than a silent gap: `scripts/check_dashboard_coverage.js` fails when a published result name is unparseable, when a card has no measurements behind it, or when a charted collection is missing from either `COLLECTIONS` array or from the CI benchmark suite. Closes [#301](https://github.com/marius-bughiu/Celerity/issues/301).
22
30
-**The coverage gate measured only one of the six shipped packages.** Coverlet's assembly filter is exact-match, so `Celerity.Hashing`, `Celerity.Primitives`, and the three showcase packages had been outside the gate since the 2.0.0 package split — any of them could have dropped to 0% with CI green. All six are now measured, the gaps that exposed are backfilled to **100% line and branch** coverage, and the floor is raised from 95%/90% to match. Closes [#314](https://github.com/marius-bughiu/Celerity/issues/314).
Copy file name to clipboardExpand all lines: CONTRIBUTING.md
+23-3Lines changed: 23 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -76,15 +76,35 @@ dotnet run -c Release -- --filter '*' # run everything with the default (slow, h
76
76
77
77
### CI
78
78
79
-
The `benchmarks` job in [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs the full suite on `ubuntu-latest` after `build-and-test` succeeds. It uses a faster `CiConfig` (3 warmup × 5 measurement iterations) so the whole suite completes in ~5 min.
79
+
[`.github/workflows/benchmarks.yml`](.github/workflows/benchmarks.yml) runs the CI-tracked core suite (the `CoreBenchmarks` array in `Program.cs`) at full BenchmarkDotNet accuracy, sharded across a parallel matrix. On a PR each shard measures its slice of both the PR head and the `main` tip back-to-back on the same runner, so hardware variance cancels; an aggregate job stitches the shard reports back together.
80
80
81
81
Results are parsed by [`benchmark-action/github-action-benchmark`](https://github.com/benchmark-action/github-action-benchmark) and:
82
82
83
-
-**On a PR**: a comment is posted with the comparison vs the last `main` baseline. If any benchmark regresses by more than **200%** (i.e. is 2× slower or worse), the job fails red. The threshold is deliberately loose because GitHub-hosted runners are noisy — we'll tighten it once we have history to calibrate against.
84
-
-**On a push to `main`**: the new measurement is appended to the `gh-pages`-stored history powering the dashboard at `https://marius-bughiu.github.io/Celerity/dev/bench/` (enable Pages on the `gh-pages` branch once the first run creates it).
83
+
-**On a PR**: a comment is posted with the same-runner A/B comparison vs `main`. Rows that move by more than ±10% *and* beyond the combined standard deviation of both measurements are flagged; the flags are advisory, so a noisy row does not fail the job.
84
+
-**On a push to `main`**: the new measurement is appended to the `gh-pages`-stored history powering the dashboard at <https://marius-bughiu.github.io/Celerity/dev/bench/>.
85
85
86
86
If a change is motivated by performance, include before/after numbers from a local Release run in the PR description — the CI job is a guardrail, not a precision instrument. Numbers without `-c Release` are not useful — BenchmarkDotNet refuses to run in Debug.
87
87
88
+
### The dashboard
89
+
90
+
The dashboard reads BenchmarkDotNet result *names*, so a benchmark's naming is part of its contract with the site. A name that the page's parser does not recognise is dropped silently — the data publishes to `gh-pages` correctly and the card just renders blank, with nothing red anywhere.
91
+
92
+
Two rules keep a benchmark chartable:
93
+
94
+
- Methods are named `{TypeName}_{Op}` — `EnumSet_Contains`, `Dictionary_Lookup`. The type name decides whether the row is the Celerity arm or the BCL baseline (`BCL_TYPES` in the dashboard source), and `{Op}` is what the card is titled.
95
+
- A `[Params]` sweep property must be called **`ItemCount`**. A class may declare no sweep at all — `EnumMap` / `EnumSet` are bounded by the enum universe, so a synthetic item count would chart a dimension that does not exist — in which case the dashboard renders a single bucket. Any *other* property name is rejected rather than charted under an "items" label it does not mean.
96
+
97
+
Adding a collection to the site means updating three lists by hand, since the published data alone does not tell the page what to draw: the ship card in `web/index.html`, and the `COLLECTIONS` array in **both**`web/dev/bench/index.html` and `web/dev/bench/detail.html` (`items: [NO_SWEEP]` for an unparameterized class).
98
+
99
+
[`scripts/check_dashboard_coverage.js`](scripts/check_dashboard_coverage.js) enforces all of this so the failure mode is a red check rather than a blank card. It lifts the `COLLECTIONS` tables and the name parsers out of the dashboard HTML rather than reimplementing them, so it validates the code that actually ships. Run it any time you touch a benchmark or the dashboard:
node scripts/check_dashboard_coverage.js path/to/joined-report-full.json # + verify the data
104
+
```
105
+
106
+
The structural half — the two `COLLECTIONS` arrays agree, and every charted collection has a `{Key}Benchmark` registered in `CoreBenchmarks` — runs on every PR in `ci.yml`. The full check runs in the aggregate job of `benchmarks.yml`, against the merged report, and additionally asserts that every published name parses and that every card resolves to both a BCL and a Celerity measurement.
107
+
88
108
## Versioning
89
109
90
110
Celerity uses [MinVer](https://github.com/adamralph/minver) to derive NuGet package versions exclusively from **git tags**. There is no `<Version>` or `<PackageVersion>` property in any `.csproj` file — the single source of truth is the `v`-prefixed annotated tag on the commit that represents a release.
0 commit comments