Performance

Measured throughput and latency of dxn-signalr against the official .NET client, how the comparison is run, and how to read it.

The repository measures the client against the official .NET client (Microsoft.AspNetCore.SignalR.Client 10) in the same run. Both clients use release builds and identical warm-ups, and each figure is the median of three alternating rounds. The harness checks two budgets on those figures: latency at most 1.10 times the .NET client’s, and throughput at least 0.95 times.

#Results

The figures below are the budget-checked full-scale run of 2026-10-08, recorded in the library’s architecture notes. It ran on Windows 11 (10.0.26300), x64 with 16 logical processors, .NET 10.0.0 and rustc 1.92.0. The client, the test service and the .NET reference shared the machine. Every budget held.

WorkloadProtocolRust client.NET clientRatio
Invoke latency, p50 / p99JSON159.6 / 194.8 µs163.4 / 200.1 µs0.98x / 0.97x the latency
Invoke latency, p50 / p99MessagePack158.6 / 192.1 µs164.0 / 198.0 µs0.97x / 0.97x the latency
Send throughputJSON690,585 sends/s700,756 sends/s0.99x
Send throughputMessagePack668,365 sends/s385,706 sends/s1.73x
Server streamJSON1,056,953 items/s411,642 items/s2.57x
Server streamMessagePack1,212,761 items/s789,110 items/s1.54x
Fan-in (8 clients)JSON42,698 calls/s30,170 calls/s1.42x
Fan-in (8 clients)MessagePack42,717 calls/s30,694 calls/s1.39x

How to read them:

  • Invoke latency is on par with the .NET client.
  • Streaming and fan-in are consistently faster, which the library attributes to its single-pass wire codec.
  • Send throughput is close to the .NET client’s and varies most between runs, because each send completes once the transport accepts the frame, as SendAsync does. The .NET client’s own MessagePack send rate ranged from about 390,000 to 710,000 sends/s across runs, and an earlier full run measured the Rust client at 0.92x for send throughput. Treat the 1.73x as noise in the reference, not as a lead.

#Workloads

WorkloadTimed work (full scale; quick is a tenth)Metrics
invoke-latency2,000 sequential invocationsp50_us, p99_us, max_us
stream-throughputone server stream of 100,000 itemsitems_per_second
send-throughput50,000 sends acknowledged by a final invocationsends_per_second
fan-in8 concurrent clients each invoking 1,000 timesaggregate calls_per_second

Each workload warms up first on the measured connection for at least two seconds, so the .NET client’s tiered JIT settles before the timed window.

#Run it yourself

The comparison is the Performance and ReferenceClientBudget categories of the .NET test project, run through Aspire. It needs the .NET SDK pinned in the repository’s global.json, the Aspire CLI and Rust. From a checkout of the repository:

DXN_SIGNALR_PERF_WORKLOADS=all DXN_SIGNALR_PERF_SCALE=full dotnet test dotnet/dXn.SignalR.E2E.Tests --filter "Category=Performance|Category=ReferenceClientBudget"

DXN_SIGNALR_PERF_SCALE=quick is a smoke run. The GitHub Actions workflow perf / compare runs the same on demand and uploads the report as the perf-report artifact.

For the crate’s internal hot paths there are Criterion micro-benchmarks over the codec and action storage:

cargo bench --bench hot_paths --features messagepack,bench-internals

The bench-internals feature is unstable and exists only for these benchmarks; applications never enable it.

#Next

Testing describes the harness that produces these numbers.