Server, client, processing-only control and selftest for the shared benchmark, using asynchronous Socket APIs and Utf8JsonReader, with no NuGet dependencies. The repository README covers campaigns and results; the contract defines the protocol, validation, load model and result schema.
From the repository root in PowerShell:
dotnet build src/dotnet/Bench.csproj -c Release
dotnet src/dotnet/bin/Release/net11.0/Bench.dll selftest --corpus tests/fixtures/golden.bin
./tests/run_all.ps1
dotnet format whitespace src/dotnet/Bench.csproj --verify-no-changesThe SDK (11.0.100-preview.7.26381.103, global.json) and runtime (11.0.0-preview.7.26381.103, Bench.csproj) are pinned with roll-forward disabled. The project targets x64 Release with Server GC and concurrent GC, with DATAS (the dynamic heap adaptation that .NET 9+ enables by default) turned off for throughput; the campaign also runs the allocation-heavy cells with the runtime default, invariant globalization, and warnings as errors. Tiered compilation and dynamic PGO are runtime defaults; campaign warmups (15–20 s) cover tier-up.
| File | Responsibility |
|---|---|
| Program.cs | Entry point, options (same names, ranges and rules as the native parser), header writer, socket configuration |
| Processing.cs | Strict parsing with Utf8JsonReader, canonical digest, owned batches, retention and eviction, epoch summaries, budget |
| FastJson.cs | The alternative schema-specific parser (--parser fast): same rules, SIMD string scanning, integer key matching, SWAR integers |
| Utf8Check.cs | AVX2 UTF-8 validation (the Keiser-Lemire lookup algorithm used by simdjson) for the fast parser |
| Server.cs | Asynchronous server: one loop per connection, per-frame Task.Yield, 10 ms deadline scanner, measured interval |
| Client.cs | Load generator: per-connection sender and ACK reader tasks, closed-loop credits, raised-priority generator thread, pacing |
| Corpus.cs | Corpus loading and the processing-only control |
| Report.cs | Histogram, JSON output, resource snapshots, build metadata |
| SelfTest.cs | In-process checks, case for case with the native selftest, run once per parser |
| SelfTestFuzz.cs | Differential fuzzing: fast parser vs Utf8JsonReader, Utf8Check vs Utf8.IsValid, inputs placed against a no-access guard page |
- Parsing.
Utf8JsonReader.Read()validates JSON structure and escape syntax but not string UTF-8 or surrogate pairing. An unescaped name orkindthat equals a valid value is plain ASCII and is matched in place; anything else is decoded withCopyString, which unescapes and rejects lone surrogates and invalid UTF-8. Unescaped messages are checked in place withUtf8.IsValidand hashed or retained directly; escaped messages useCopyStringinto a scratch larger than the 4,096-byte limit (it rejects an exact fit), then the decoded length is checked. Together these measured +13–17% on EASY content and no change on HARD content. Two hand-written unescapers measured 7–13% slower thanCopyStringand were discarded (diagnostics). Integers are validated and parsed from the raw token in one pass, as in C++. The FNV digest uses a plain loop: a force-inlined unrolled form measured about 20% slower overall. - Retention. Rows and text are stored in chunks, identically to C++: the first chunk grows from 16 rows / 256 bytes by doubling into uninitialized arrays (
GC.AllocateUninitializedArray, likestd::vector::reserve), later chunks hold 1,024 rows (48 KiB) or 64 KiB of text. No array reaches the large-object heap, whose collections are gen2-only. Reuse keeps evicted batches on a free list (at mostretain-batches + 1batches); allocate creates fresh batches and drops evicted ones for the GC. - Server. Receives and sends complete synchronously when data is ready (the runtime's inline completion). After each response the connection awaits
Task.Yield()so connections take turns on the thread pool. Deadlines are checked at every completion, by a 10 ms scanner that disposes expired sockets, and every 1,024 records during processing.--workersis recorded only; the CPU mask (andDOTNET_PROCESSOR_COUNT, set by the runner) is the budget. - Client. Closed loop uses a per-connection
SemaphoreSlimofwindow / connectionscredits; scheduled load uses a dedicated highest-priority thread with a high-resolution waitable timer and at most 1 ms of spin, admitting against globalInterlockedcaps. Each request is one gatheredSendAsync(header and payload). Acknowledgements are read in batches, never past what is owed. - Counters.
allocated_bytesisGC.GetTotalAllocatedBytes(precise: true)over the interval;gcgives collection counts andGC.GetTotalPauseDuration()over the same interval. Pause totals are runtime observations, not a latency attribution. - Fast parser.
--parser fastswapsUtf8JsonReaderfor FastJson, written for this one schema. It validates UTF-8 once per frame, scans strings 64 bytes at a time for",\and control bytes, matches the seven keys with one or two integer compares, parses integers eight digits at a time, and decodes escapes with speculative 32-byte copies. It accepts and rejects exactly what theUtf8JsonReaderpath does. The selftest fuzzes one against the other (60,000 documents, both retention paths) with every input ending at a no-access page, and planted bugs, including a one-byte over-read, are caught.BENCH_PARSER=fastmakes it the default so the shared test suites can run it; the campaign runner always passes--parserexplicitly. Results are in the parser challenge.