JSON-RPC.NET

Micro-benchmarks#

BenchmarkDotNet timings of one request on one core, per request shape, with an allocation column. Use these to judge a change to the dispatch path; use TestServer_Console --sync for throughput under load and the tables in the main README.

dotnet run -c Release --project benchmarks/Micro -- --filter '*'

Useful arguments: --filter 'AustinHarris.JsonRpc.Micro.DispatchBenchmarks.*' for one class (*Dispatch* would also match AsyncDispatchBenchmarks), --job short for a quick look (3 warm-up and 3 measured iterations), --disasm to print the JIT disassembly of each benchmark.

Benchmark classes:

Read the Allocated column first: a non-zero value on a numeric shape means the request touched the GC, which the fast path must not do. Then compare Mean, but only between runs on an idle machine or within one run: background load biases ratios as well as absolute numbers, which is why BindingComparisonBenchmarks puts both registrations in one process.

These rows run on one thread, so they cannot detect a process-wide serialization point. The AsyncScratch pool lock capped ProcessAsync at about 4 M RPC/s on every core count while every inline None row here stayed at 0 B and the same mean. Before a release, and after any change to dispatch, pooling or the async path, also run the scaling gate on the reference machine and paste its table into the release notes:

dotnet run -c Release --project TestServer_Console -- --scale 3 16 4.0

It fails when an inline row scales less than 4× from 1 to 16 workers. The lock gave 1.3; the per-thread cache gives about 7. The pull-request build runs a diagnostic --scale 3 4 2.0 on the shared runner. It also checks that every lock, Interlocked, Volatile.Write, thread-static and writable static field on the request-path files is listed in .github/request-path-sync.allowlist with a reason.