Skip to main content

Performance

In the original Linux Redis-container measurements, Respire is faster for concurrent and pipelined GETs against both StackExchange.Redis versions. A second 3.4.5 run retains the concurrent advantage but classifies pipelined GET as Same. Most single-operation timings against 3.4.5 show no established latency advantage. Against 4.0.127-alpha, the ordinary-operation and local GET cache-hit comparisons favor Respire. Publish-to-consume pub/sub shows no established latency advantage: the latest runs tie both versions, and an earlier 4.0 run was slower. These results describe the measured commits and rigs, not a guarantee for every workload.

Measured comparisons: October 2026​

Each table compares clients within the same run. The 3.4.5 and 4.0.127-alpha runs used different hosted machines; do not compare their absolute timings to infer a version regression. Likewise, the replacement pub/sub runs cannot isolate the cost of the delivery-gap guard. 4.0.127-alpha is a prerelease dependency, not a claim about every 4.0 release.

Timings below are mean ± BenchmarkDotNet Error (half the 99.9% confidence interval). Faster, Same, and Slower reproduce the report's configured MannWhitney(5%) comparison against StackExchange.Redis. Same means this comparison did not establish a difference; it is not proof of equivalence. A lower point estimate alone is not called a win. The evidence archives retain standard deviations, medians where emitted, raw measurements, outliers, allocation data, and full logs. Allocation figures are measured bytes per operation; zero means no allocation reported by this measurement, not a universal API guarantee.

Single operations, sequential loops, and large values​

CommonOperationsBenchmarks uses uncached server operations. Times are in microseconds. The x100 sequential rows are normalized per command; LPUSH+LPOP and SET+DEL measure the whole two-command sequence. The SET payloads named 1KB and 10KB contain 1,024 and 10,240 ASCII bytes. This suite does not measure large-value GET or arbitrary payload sizes.

StackExchange.Redis 3.4.5​

OperationRespire μsStackExchange μsAllocated Respire / StackExchangeRespire classification
EXISTS108.120 ± 0.8404109.080 ± 0.54490 B / 296 BSame
GET108.447 ± 0.6875108.916 ± 0.756548 B / 504 BSame
GET long108.388 ± 0.4209108.991 ± 0.44050 B / 416 BSame
GET x100 sequential89.151 ± 1.018693.943 ± 0.931750 B / 338 BSame
HGET108.143 ± 0.6267108.667 ± 0.804548 B / 520 BSame
HSET109.850 ± 0.4958109.674 ± 0.59020 B / 328 BSame
INCR108.765 ± 0.5386109.459 ± 0.55440 B / 296 BSame
LPUSH+LPOP205.466 ± 1.7639207.979 ± 1.2407256 B / 760 BSame
PING108.190 ± 0.5336108.415 ± 0.58760 B / 303 BSame
PING x100 sequential88.859 ± 1.106392.409 ± 0.93672 B / 242 BSame
SADD108.965 ± 0.6074109.482 ± 0.64750 B / 312 BSame
SET 10KB112.989 ± 0.8238115.088 ± 1.39190 B / 312 BSame
SET 13B109.257 ± 0.6119110.387 ± 0.69580 B / 312 BSame
SET 1KB109.930 ± 0.7464110.148 ± 0.89720 B / 312 BSame
SET x100 sequential91.513 ± 1.072496.416 ± 1.44052 B / 250 BSame
SET+DEL201.940 ± 1.0721205.198 ± 1.0812200 B / 648 BSame
SISMEMBER108.239 ± 0.7833109.688 ± 0.47270 B / 312 BSame

StackExchange.Redis 4.0.127-alpha​

OperationRespire μsStackExchange μsAllocated Respire / StackExchangeRespire classification
EXISTS97.716 ± 2.2846113.468 ± 0.54870 B / 518 BFaster
GET90.781 ± 2.2935113.737 ± 1.512848 B / 718 BFaster
GET long95.943 ± 1.7497114.927 ± 0.66540 B / 632 BFaster
GET x100 sequential81.109 ± 1.269892.215 ± 2.305050 B / 205 BFaster
HGET101.495 ± 2.1170116.696 ± 0.860748 B / 718 BFaster
HSET103.962 ± 2.9925115.521 ± 1.03450 B / 520 BFaster
INCR98.503 ± 2.6063115.415 ± 0.93730 B / 519 BFaster
LPUSH+LPOP198.477 ± 1.5328225.154 ± 1.8793256 B / 858 BFaster
PING92.371 ± 2.7828112.531 ± 0.82810 B / 541 BFaster
PING x100 sequential79.714 ± 0.930790.173 ± 0.94162 B / 133 BFaster
SADD98.382 ± 1.5969113.370 ± 0.69440 B / 519 BFaster
SET 10KB118.639 ± 0.4647131.878 ± 0.72200 B / 432 BFaster
SET 13B99.405 ± 2.5217114.985 ± 1.71700 B / 520 BFaster
SET 1KB102.708 ± 2.3269119.035 ± 0.70050 B / 432 BFaster
SET x100 sequential81.584 ± 1.143395.635 ± 1.11882 B / 109 BFaster
SET+DEL191.925 ± 1.5237219.893 ± 2.2582200 B / 729 BFaster
SISMEMBER100.462 ± 1.9337113.863 ± 0.79260 B / 520 BFaster

The 4.0 run warns that Respire HGET and HSET have multimodal distributions. Their reported means favor Respire, but that variability limits interpretation; inspect the preserved samples and standard deviations (3.169 μs for HGET, 4.479 μs for HSET) before extrapolating. All 17 original ordinary-operation ties against 3.4.5, including SET 10KB, are dispositioned in the server-latency investigation below. The older 10KB SET investigation concerned a different, noisy run; these measurements do not establish a continuing 15% regression.

The comparison workflow has an opt-in server-latency-diagnostics dispatch input for that investigation. It runs the unchanged CommonOperationsBenchmarks methods and the four uncached ClientSideCachingBenchmarks pairs, including concurrent/pipelined GET controls, with the selected BenchmarkDotNet job and the existing 5% statistical test. Use the 3.4.5 dependency and medium job when reproducing these ties.

Separate diagnostic processes call those same methods after warmup, recording process CPU time, wall time, process-wide allocations, assembly hashes and endpoint identity. On one runner, a host-network Redis control is bracketed by two Docker port-mapped passes using the same image ID. A fourth pass captures managed CPU stacks, runtime threading/contention events and raw EventPipe traces. Artifacts retain all 46 cases (21 pairs plus two control pairs); missing cases or unusable CPU traces fail the run.

These async-loop diagnostics are not BenchmarkDotNet measurements. Their process totals include fixture background clients and harness work, and the traced pass includes profiler overhead. Summed CPU time across threads cannot be subtracted from elapsed time to infer waiting; managed samples cannot attribute kernel scheduling or Redis server CPU. The host-network comparison changes the network path and port together and can suggest where to investigate, but cannot alone prove a scheduler cause. The completed analysis follows.

Server-latency investigation outcome​

#1744 concludes with no production optimization justified by this profile. The repeat contains 16 remaining ties and five Faster results among the original 21 ties. Runtime scheduling paths dominate the managed sample counts, and the separate topology control has a much larger effect than the between-client differences. This supports a substantial environment/runtime contribution; it does not prove that client overhead is zero or identify a removable critical-path cost. The remaining ties are a documented limitation, not deferred validation or claimed wins.

The hosted repeat run 38695907419 measured source commit 8fd9ea63b49b6e13981c674f2f4d6414dc248fa0 against StackExchange.Redis 3.4.5, on an AMD EPYC 9V74, four logical/two physical cores, Ubuntu 24.04.5, kernel 6.17.0-1022-azure, runner image ubuntu24 20261004.327.1. It used net10.0, SDK 10.0.402, runtime 10.0.13, BenchmarkDotNet 0.15.8, X64 RyuJIT x86-64-v4, Medium/two launches, 10 warmups and 15 measurement iterations per launch. The benchmark filter selected *CommonOperationsBenchmarks.* and *ClientSideCachingBenchmarks.*ServerRead; neither workload nor MannWhitney(5%) changed. Redis was 8.10.2, with the same image ID recorded in the original provenance below. The original run used an Intel Xeon Platinum 8573C and a different Respire commit. Thus these are original/repeat observations, not a controlled before/after optimization experiment. No source speedup is inferred from a change in absolute timing or classification.

All 21 original ties and both regression controls appear below. Times are mean ± Error in μs, and allocations are bytes per operation (Respire / StackExchange). The original means and allocations remain in the preceding and following historical tables. Each classification compares clients only within its own run. Sequential and overlapping GET normalization is unchanged; overlapping GET times are amortized per completed command.

ScenarioRespire μsStackExchange μsAllocated R / SEOriginal → repeat
EXISTS95.861 ± 3.9044102.744 ± 0.62350 / 295 BSame → Same
GET99.934 ± 1.8565110.771 ± 1.221948 / 502 BSame → Faster
GET long100.312 ± 2.3864109.751 ± 0.82710 / 415 BSame → Faster
GET x100 sequential80.970 ± 0.386385.980 ± 0.677050 / 338 BSame → Same
GET x200 pipelined1.407 ± 0.00721.514 ± 0.038549 / 289 BFaster → Same
GET x50 concurrent3.055 ± 0.01913.324 ± 0.052352 / 291 BFaster → Faster
HGET99.962 ± 1.8843109.106 ± 1.424348 / 520 BSame → Faster
HSET104.186 ± 1.6189104.983 ± 1.05890 / 328 BSame → Same
INCR98.713 ± 2.4484100.479 ± 2.02740 / 296 BSame → Same
LPUSH+LPOP200.581 ± 1.3785207.913 ± 3.7299256 / 760 BSame → Same
PING93.805 ± 3.426397.065 ± 2.15780 / 303 BSame → Same
PING x100 sequential78.143 ± 0.684582.237 ± 0.96882 / 242 BSame → Same
SADD100.800 ± 1.809996.493 ± 4.19280 / 312 BSame → Same
SET 10KB120.382 ± 1.1587117.864 ± 0.72270 / 312 BSame → Same
SET 13B103.051 ± 1.3332103.948 ± 0.82170 / 311 BSame → Same
SET 1KB101.637 ± 2.1314105.679 ± 1.03680 / 312 BSame → Same
SET x100 sequential83.516 ± 0.574686.249 ± 0.50782 / 250 BSame → Same
SET+DEL192.682 ± 1.2953207.056 ± 1.5008200 / 648 BSame → Faster
SISMEMBER101.225 ± 1.617498.654 ± 1.66090 / 312 BSame → Same
Uncached / EXISTS hot100.552 ± 1.51099.756 ± 1.8840 / 296 BSame → Same
Uncached / GET hot101.595 ± 1.200107.830 ± 2.71164 / 527 BSame → Same
Uncached / GET missing hot101.998 ± 1.730109.350 ± 1.3060 / 416 BSame → Same
Uncached / HGET hot103.874 ± 0.757111.563 ± 0.97464 / 543 BSame → Faster

Every Same → Same row retains the limitation: no latency advantage was established. The five Same → Faster rows establish an advantage only in this repeat, without attributing it to a code change. No row is Slower. The pipelined control loses its established advantage, despite a lower point estimate; this must not be hidden by the concurrent control's Faster result. Neither control establishes a historical regression across rigs. Respire's reported allocations match the original run in all 23 scenarios, and remain below StackExchange's; that does not override any latency tie. The repeat warns of bimodality for Respire EXISTS and StackExchange SADD; the archived logs and samples retain that variation.

Separating process work from topology effects​

The diagnostics use fresh processes, two seconds of warmup and three seconds of measurement per case, separately from BDN. The same Redis image runs first through Docker port mapping on 127.0.0.1:6379, then host networking on 127.0.0.1:6380, then the original mapped endpoint. All 46 client/method cases have lower host-network wall time than both bridge passes. For Respire's 21 investigated cases, host wall time is 46.5–49.3% of bridge-a wall time. This is a descriptive range across cases, not a confidence interval. Fixed phase order, different Redis instances/ports, and the short diagnostic window limit causal attribution.

Selected observations below are wall μs / process CPU μs per operation. CPU is summed across all process threads and can exceed wall time. It includes fixture background clients, runtime scheduling and the diagnostic adapter, and excludes the separate Redis process. Subtracting it from wall time would not measure network waiting.

CaseBridge-a wall / CPUHost wall / CPUBridge-b wall / CPU
Respire PING80.24 / 155.5638.32 / 81.9580.09 / 157.82
StackExchange PING82.38 / 160.0136.75 / 103.5182.66 / 158.65
Respire GET81.78 / 159.2339.12 / 83.1780.96 / 158.12
StackExchange GET89.20 / 170.6837.92 / 106.2485.74 / 169.10
Respire SET 10KB93.74 / 173.0743.59 / 92.4697.95 / 174.50
StackExchange SET 10KB107.47 / 183.5443.32 / 122.12100.88 / 183.31

Process allocation totals also include the adapter: for example Respire GET is about 168 B/op in each plain diagnostic phase, versus 48 B/op in BDN. Diagnostic totals must not be published as library allocation regressions. The machine-readable analysis retains all 184 observations, including the separate, instrumented bridge-profile pass.

The 46 trace summaries contain 1,299,655 samples: 246,352 Managed and 1,053,303 External, with no missing stacks or lost events and one measurement boundary pair per trace. External samples include waiting threads; they are not CPU attribution. For the 21 Respire cases, 73.9–77.4% of Managed samples contain System.Threading.LowLevelLifoSemaphore frames. This counts inclusive stacks, not a percentage of request latency. In the GET pair:

Managed sample countRespire GETStackExchange GET
All Managed samples5,2615,452
With thread-pool semaphore frames3,9924,124
With socket event-loop frames602595
With the measured client's networking frames411104
With diagnostic adapter frames40394

The last client row matches Respire.Networking. and StackExchange.Redis. respectively. Inclusive counts can overlap; a client frame deep in an adapter continuation does not make the whole stack client overhead. Native frames can be unresolved even when a stack is present. The largest Respire GET client stack (284 samples) is in the socket send path, with completion, the adapter and the next request on the same stack. The sampled source already batches FIFO completions and delivers drained replies on the receive thread through DeliverThenReceiveCore; the profile does not expose a redundant per-reply dispatch to remove.

The decision is to retain production behavior. Changing global thread-pool spinning, reordering response delivery, or removing cancellation/ownership checks would not follow from these samples. Managed sampling does not resolve kernel off-CPU time, packet transit, or Redis server execution, so it cannot establish an exact CPU/network split. A controlled same-rig intervention with kernel/server attribution would be needed to justify such a change. No optimization was made, so there is no claimed before/after speedup requiring a new run. This completes the issue's documented-limitation outcome while preserving FIFO, cancellation and ownership semantics.

Reproduce and retain this analysis​

The measurement archive contains raw BDN samples, reports and logs, rig and container metadata, resolved dependencies, case manifests, observations, assembly hashes and all stack summaries. The trace archive retains all 46 original cpu.nettrace files. Only the 46 derived .etlx indexes are omitted; the diagnostic tool's --summarize TRACE REPORT command regenerates them from the raw traces. The SHA-256 manifest records both archives, every retained artifact file, the omitted indexes and analysis files. Archived bytes were verified against the downloaded artifact; these copies survive Actions artifact expiry.

Extract both archives into one directory, then use the downloadable analysis script with the original 3.4.5 archive linked in the provenance table. Save the manifest beside the script; it verifies every consumed measurement and stack summary, each expected raw trace, and the baseline archive against the manifest's SHA-256 hashes before producing output. It also checks the recorded sizes of the extracted files:

python analyze.py extracted 38484247008.zip > analysis.json

The script checks case coverage, operation normalization, unchanged diagnostic assemblies, measurement boundaries and trace completeness. It copies BDN's classifications without changing its statistical test. The checked-in analysis output contains all original/repeat means and allocations, diagnostic totals, sample-type counts and explicitly named inclusive stack markers.

Concurrent throughput and pipelines​

Both methods issue overlapping GETs through the normal multiplexed API, then await every reply. The x200 case increases pipeline depth; it does not call an explicit batch API. OperationsPerInvoke already divides elapsed time by 50 or 200. These are amortized costs per completed GET, not individual request latency. Approximate throughput below is 1,000,000 / mean_microseconds; multiplying by the concurrency again would overstate it.

StackExchange versionWorkloadRespire μs/GETStackExchange μs/GETGET/s Respire / StackExchange (approx.)Allocated Respire / StackExchangeRespire classification
3.4.5GET x200 pipelined1.630 ± 0.01171.870 ± 0.0870613,497 / 534,75949 B / 289 BFaster
3.4.5GET x50 concurrent3.061 ± 0.02533.439 ± 0.0454326,691 / 290,78252 B / 291 BFaster
4.0.127-alphaGET x200 pipelined1.373 ± 0.00521.716 ± 0.0652728,332 / 582,75149 B / 505 BFaster
4.0.127-alphaGET x50 concurrent3.037 ± 0.00943.681 ± 0.0644329,272 / 271,66552 B / 501 BFaster

Publish-to-consume pub/sub​

These measurements use RESP3 on both clients, one consuming subscriber, and an 18-byte Hello, subscriber! payload. The timed operation awaits publication and consumption of the message. Respire throws if the subscription ends or reports a delivery gap. This measures delivery, not just the PUBLISH acknowledgement, and does not cover fan-out.

The latest unprofiled Medium/two-launch runs at 21eee745 report:

StackExchange versionRunRespire μsStackExchange μsAllocated Respire / StackExchangeRespire classification
3.4.538696158383233.9 ± 1.15230.9 ± 1.21400 B / 672 BSame (ratio 1.01)
4.0.127-alpha38696161089124.7 ± 0.75125.7 ± 1.46400 B / 1064 BSame (ratio 0.99)

The earlier corrected runs at 7cd38895 remain valid observations on their own rigs:

StackExchange versionRespire μsStackExchange μsAllocated Respire / StackExchangeRespire classification
3.4.5225.7 ± 0.89222.9 ± 1.41400 B / 672 BSame
4.0.127-alpha174.0 ± 1.30154.5 ± 3.85399 B / 1046 BSlower

The 3.4.5 comparison remains Same. The earlier 4.0 Slower result (ratio 1.13) used AMD EPYC 9V74; the new tie used Intel Xeon Platinum 8573C. This does not demonstrate an improvement or a regression between versions or commits. Lower allocation in all four runs does not cancel the latency findings. The original full-suite pub/sub reports lack the timed delivery-gap guard and remain superseded; both tables here retain that guard.

Acknowledgement and consumer diagnostics​

The follow-up investigation separates time to the PUBLISH acknowledgement from the remaining wait through consumer completion and UTF-8 decoding, and records how often the subscriber read is already complete. Delivery can overlap acknowledgement, so the remaining phase is not an independent measure of delivery latency. Both phases retain real message consumption and the delivery-gap guard.

Each new run also retained 5,000 samples per client after 500 warmups, alternating client order, validating payload and recipient count, and recording synchronous read completion. Times below are instrumented diagnostic microseconds, not client performance comparisons:

DependencyClientAck mean / P95After-ack mean / P95 / P99Synchronous reads
3.4.5Respire201.174 / 261.48929.779 / 75.855 / 98.5341571 / 5000
3.4.5StackExchange208.874 / 271.21728.022 / 75.291 / 99.3361748 / 5000
4.0.127-alphaRespire128.091 / 163.57116.943 / 39.922 / 54.7962389 / 5000
4.0.127-alphaStackExchange157.326 / 186.2505.705 / 24.136 / 43.8843850 / 5000

The 3.4 residual distributions are similar. Against 4.0, Respire's residual mean is 11.238 μs longer, while its acknowledgement mean is 29.235 μs shorter. A subscriber has more time to become ready when acknowledgement takes longer: the residual and synchronous-read differences alone cannot establish slower delivery or a redundant hop. The probe also adds timestamps, cancellation/deadline handling and validation; its phase sums must not replace the unprofiled comparison above.

The separate ShortRun EventPipe/threading passes show these paths in both Respire traces: PublishAsync, FlushLoopAsync, ReceiveLoopAsync, CompletionScheduler.ExecuteCore, SubscriptionHub.EnqueueMessage, SubscriptionBuffer.Write, the bounded readiness channel, and SubscriptionBuffer.ReadAllAsync. The StackExchange traces include RedisSubscriber.PublishAsync, ChannelMessageQueue.Write and ConnectionMultiplexer.OnMessage; the 4.0 trace also includes PubSub.PublishAsync and RespPushDispatch.Deliver. This locates the publication, delivery and consumer paths, but does not identify a removable bottleneck. All four traces also include connection/handshake setup, BDN engine work, thread-pool waits/spinning, GC-related frames and unresolved ?!? leaves. Setup creates both clients even when only one client's method is benchmarked.

The derived analysis retains exact observed frame names and recalculated phase percentiles. Its stack inventory uses positive-duration exported intervals ending in CPU_TIME to establish frame presence only. Whole-process inclusive weights, exporter intervals and runtime wait frames must not be interpreted as per-operation CPU cost, kernel scheduling delay, or Redis server time. The raw .nettrace files remain available for deeper attribution.

Threading diagnostics reported completed work items per operation of 3.8389 / 4.4043 (Respire / StackExchange 3.4) and 3.6802 / 5.2412 (Respire / StackExchange 4.0). Lock contentions were 0.0210 / 0.0054 and 0.0034 / 0.0176, respectively. These process-wide instrumented counts do not isolate the subscription buffer or the critical path. Neither a consistent excess of Respire work items nor a consistent contention disadvantage appears across the two rigs; fewer work items alone cannot establish lower latency.

Investigation outcome​

#1745 concludes with no safe latency improvement demonstrated by this evidence, using its explicit profiling-only outcome. The code review considered these changes:

CandidateEvidence and disposition
Inline readiness continuationsSubscriptionBuffer.Write signals while holding the buffer gate, within the hub's routing gate. Enabling synchronous continuations could run consumer work on the receive path under those gates. The traces and work-item counts do not establish a redundant hop worth that change.
Remove buffering or routing synchronizationThe hub gate orders epoch checks and enqueueing against reconnect markers. The buffer gate protects bounded messages and ordered/coalesced gaps. Contention counts do not attribute a latency bottleneck to either gate; removing them would require a new correctness design.
Skip payload ownership or decodingThe receive payload is copied once for the subscription targets, and the benchmark consumes text. The profiles do not establish copying/decoding as the cause; bypassing ownership or returning before consumption would change the operation being measured.

These decisions follow the measured-source SubscriptionBuffer and SubscriptionHub. No runtime behavior changed in this conclusion, so there is no new delivery, gap, ordering, cancellation or disposal behavior to validate. This is not proof that optimization is impossible. A future candidate needs an isolated attribution experiment, a controlled before/after comparison on the same rig, and correctness coverage for those contracts. Fan-out, slow consumers, reconnects and other payload sizes remain outside this measurement.

Manual comparison runs can enable profile-pubsub to reproduce the diagnostic collection. Only the unprofiled reports support the published latency/allocation comparisons; the instrumented ShortRun results do not establish a win.

Client-side cache hits and server reads​

With client-side caching, a repeated read can return from bounded local memory. Redis invalidates tracked entries when keys change. A hot-cache measurement excludes the network round trip and should not be described as a faster server read.

StackExchange.Redis 4.0.127-alpha has a built-in server-assisted cache. The dedicated ClientCacheComparisonBenchmarks suite compares local GET hits on both clients, using the 4.0 ValueTask API. Setup verifies that warmed reads do not increase Redis's GET counter. Times here are in nanoseconds:

GET hitRespire nsStackExchange 4.0.127-alpha nsAllocated Respire / StackExchangeRespire classification
Present key114.9 ± 0.15191.0 ± 1.230 B / 112 BFaster
Missing key117.3 ± 0.22135.6 ± 0.290 B / 0 BFaster

The 3.4.5 build has no corresponding built-in-cache comparison. Its absent cache-hit rows are unavailable scenarios, not failed benchmarks or inferred wins.

The separate ClientSideCachingBenchmarks suite deliberately leaves StackExchange caching disabled, including on 4.0. It compares Respire local hits with two uncached server-read controls. All times in this table are nanoseconds, so server reads are roughly 100,000 ns:

StackExchange versionOperationRespire local nsRespire server nsStackExchange server nsRespire server classification
3.4.5EXISTS hot269.9 ± 3.57108,891.0 ± 857.81109,232.4 ± 573.16Same
3.4.5GET hot125.8 ± 1.01108,594.1 ± 744.94109,178.1 ± 627.74Same
3.4.5GET missing hot128.0 ± 0.71108,265.8 ± 650.90108,988.4 ± 780.66Same
3.4.5HGET hot321.2 ± 1.38107,937.9 ± 871.69110,662.0 ± 999.26Same
4.0.127-alphaEXISTS hot275.4 ± 1.05100,314.6 ± 1,844.13113,988.5 ± 798.58Faster
4.0.127-alphaGET hot114.0 ± 0.22101,525.9 ± 1,524.58115,558.1 ± 943.43Faster
4.0.127-alphaGET missing hot117.1 ± 0.8997,328.2 ± 3,006.88114,271.2 ± 968.42Faster
4.0.127-alphaHGET hot319.8 ± 4.34103,605.2 ± 1,607.10115,272.8 ± 842.33Faster

All Respire local-hit rows in this controls suite are classified Faster than its uncached StackExchange baseline, an expected benefit of avoiding network work. They are not the 4.0 cache-versus-cache results above. Respire local hits reported 0 B in these cases. Full server-read allocations and variation are retained in the archives. The four 3.4.5 server-read ties are dispositioned in the server-latency investigation.

Provenance and preserved evidence​

The four original runs used BenchmarkDotNet 0.15.8, net10.0, SDK 10.0.402, runtime 10.0.13, X64 RyuJIT, Medium with two launches, 10 warmups and 15 measurement iterations per launch. They ran on GitHub-hosted ubuntu-latest, Ubuntu 24.04.5 LTS, runner image ubuntu24 20261004.327.1, kernel 6.17.0-1022-azure, with 4 logical / 2 physical cores. Logs report that raising process priority was denied; the measurements therefore include the hosted runner's scheduling conditions.

Redis was 8.10.2, image mirror.gcr.io/library/redis:8.10, image ID sha256:992eb34c6118138585e0d651b256e0085d1b6aaecf39dc16df5ba50cfbe1609e, reached at localhost:6379 through Docker port mapping without TLS. This is a local container comparison, not a remote deployment, Cluster, Sentinel, TLS, or .NET 8 measurement.

ScopeStackExchange.Redis resolved versionRunCPUJIT targetEvidence
Full suite, Oct 13–143.4.538484247008Intel Xeon Platinum 8573Cx86-64-v4ZIP
Full suite, Oct 13–144.0.127-alpha38484250090AMD EPYC 9V74x86-64-v4ZIP
Corrected pub/sub, Oct 153.4.538664941232AMD EPYC 7763x86-64-v3ZIP
Corrected pub/sub, Oct 154.0.127-alpha38664943357AMD EPYC 9V74x86-64-v3ZIP
Pub/sub follow-up, Oct 153.4.538696158383AMD EPYC 7763x86-64-v3ZIP
Pub/sub follow-up, Oct 154.0.127-alpha38696161089Intel Xeon Platinum 8573Cx86-64-v4ZIP

The two pub/sub follow-ups used source 21eee7459edb5cad574710907b154a29e78df9e2 from #1748, the same BDN/SDK/runtime versions, Medium settings, Redis image ID and port-mapped endpoint above. Their runner images were ubuntu24 20261012.334.1 (3.4.5) and ubuntu24 20261004.327.1 (4.0), both Ubuntu 24.04.5 with kernel 6.17.0-1022-azure and 4 logical / 2 physical cores. Each completed two unprofiled comparison cases and two separately instrumented ShortRun cases. Both unprofiled comparisons classify Respire as Same; instrumentation is excluded from those tables.

The ordinary/cache results used Respire source commit 17f617e3da1fef7e5317246db26b081be02f468f from preparation PR #1627. The only subsequent comparison-benchmark source change before that PR merged was the pub/sub delivery-gap guard; the other measurements remain attributed to their original commit. Corrected pub/sub used 7cd38895b99f4f4738caf9cd54275dee94d6d7a7. Respire was built from source (project version 1.0.0), not a published-package comparison. The full runs used filter *; replacements used *PubSubComparisonBenchmarks*.

Artifact validation confirmed 52 and 56 completed cases in the full runs and two in each replacement, with populated statistics and no failed benchmarks. Of the full-run cases, two pub/sub cases per version are excluded from publication in favor of the corrected runs. The 3.4.5 build excludes the four 4.0-only local cache cases by design. No failed or unavailable scenario is represented as a win.

The ZIPs are checked into the website and preserve every file from the downloaded artifacts: raw benchmark logs, JSON measurement reports, CSV/Markdown/HTML summaries, rig.txt, and resolved project.assets.json. Their bytes were verified after archiving. The SHA-256 manifest records archive and individual-file hashes, commits, versions, and run links. These copies remain available after the original Actions artifacts' 90-day retention expires.

The pub/sub follow-up ZIPs additionally preserve phase samples and summaries, diagnostic logs, both clients' EventPipe .nettrace and speedscope files, and instrumented reports: 22 byte-verified files per run. Their separate SHA-256 manifest records every archived file. Download both ZIPs, that manifest and analyze.py into one directory, then run python analyze.py to verify the hashes and reproduce analysis.json without running benchmarks. The archives retain full unprofiled distributions, allocations and rig/source provenance as well as the diagnostic evidence.

This completes the measured publication scope of #1625 and the pub/sub investigation #1745. The server-operation latency investigation #1744 is dispositioned above. Publication does not establish a universal performance advantage.

Automatic pipelining​

Concurrent calls share socket writes automatically. No batching mode is needed:

await Task.WhenAll(
redis.GetStringAsync("a").AsTask(),
redis.GetStringAsync("b").AsTask(),
redis.IncrementAsync("hits").AsTask());

Connection count tuning​

The default is one multiplexed connection, which usually gives the best throughput. If profiling shows large concurrent responses saturating one socket, benchmark two:

var redis = await RespireClient.ConnectAsync(new RespireOptions
{
Endpoints = { new RespireEndpoint("localhost", 6379) },
Connections = 2,
});

More connections split commands into smaller batches and add overhead. Raise this only when measurements show one connection is the bottleneck.

When explicit batches help​

Use CreateBatch to queue commands one by one and send them in one flush. See batches and transactions.

Buffer ownership​

Normal APIs return managed strings or arrays. For large payloads on measured hot paths, leased reads return pooled memory that you must dispose.

Dedicated blocking pool​

BLPOP, BLMOVE, and blocking stream reads use a dedicated connection pool, so they do not delay other commands.

Measure your workload​

benchmarks/Respire.Benchmarks covers protocol, pipeline, throughput, allocation, and container scenarios, some against StackExchange.Redis. Run it on net8.0 and net10.0:

bash ./run-benchmarks.sh -t All

The published comparison comes from benchmarks/Respire.ComparisonBenchmarks. It uses REDIS_HOST/REDIS_PORT when set, otherwise a throwaway Testcontainer:

dotnet run -c Release -f net10.0 --project benchmarks/Respire.ComparisonBenchmarks -- --filter '*'

CI publishes comparison benchmarks and stress tests weekly. Treat them as a starting point, then profile your own payloads, concurrency, server, network, and serializer. Sources are in benchmarks; transport details are in transport write evidence.