Skip to content

Add ExecuteResp vs Execute allocation/time benchmark - #3215

Merged
mgravell merged 1 commit into
mainfrom
marc/execute-resp-benchmark
Sep 10, 2026
Merged

Add ExecuteResp vs Execute allocation/time benchmark#3215
mgravell merged 1 commit into
mainfrom
marc/execute-resp-benchmark

Conversation

@mgravell

Copy link
Copy Markdown
Collaborator

Summary

  • New ExecuteRespBenchmarks: HSET (32 fields) + HGETALL against a real local server, comparing Execute/ExecuteAsync (object[] args, materialized RedisResult[]) against ExecuteResp/ExecuteRespAsync (RedisKeyOrValue[] rented per call, read via RespResult/AggregateChildren/ReadInt64, disposed per call).
  • Field values are plain integers and field names are precomputed once in [GlobalSetup], so the measured allocation reflects what Execute/ExecuteResp themselves cost rather than string-concatenation noise common to both benchmarks.
  • Each call rents/returns (or leases/disposes) its own buffer rather than sharing one across iterations, matching a single realistic call.
  • Both benchmarks return a tally (sum of the 32 written values, read back from the server) as a correctness check that the two paths agree - throws if they don't.
  • Re-enables the StackExchange.Redis ProjectReference in the benchmarks csproj, previously commented out for a supposed AsciiHash conflict with this project's own raw-generated-code benchmarks. That conflict doesn't reproduce: the library's [AsciiHash]-attributed classes are internal, and this project has no InternalsVisibleTo access to them, so nothing collides. Verified clean builds on net10.0, net8.0, and net481.

Measured (short run, illustrative): 84% less allocation per call (832 B vs 5280 B), time roughly a wash (network round trip dominates wall-clock) - consistent with docs/Execute.md's existing claim. Also spot-checked with an ad hoc allocation-scaling diagnostic (not included) that both the outbound argument buffer and the inbound leased response buffer amortize correctly - allocation-per-call stayed flat across multiple orders of magnitude of payload size on both the write and read sides, so the residual per-call cost is fixed Task/tracking-object overhead, not a buffer-pooling defect.

Test plan

  • Builds clean on net10.0, net8.0, net481
  • Ran manually against a local server (docker compose -f tests/RedisConfigs/docker-compose.yml up -d --wait) with dotnet run -c Release -f net10.0 -- --filter "*ExecuteRespBenchmarks*"; both benchmarks pass their internal tally correctness check

…sp/ExecuteRespAsync

HSET (32 fields) + HGETALL against a real local server, contrasting the classic
object[]/RedisResult path against ExecuteResp's RedisKeyOrValue/RespResult path. Each
call rents/leases its own buffer rather than sharing one across iterations, to match a
single realistic call rather than an amortized-lease scenario. Field values are plain
integers and field names are precomputed once, so the measured allocation reflects what
Execute/ExecuteResp themselves cost rather than string concatenation in the benchmark
code; both benchmarks return a tally read back from the server as a correctness check
that the two paths agree.

Also re-enables the StackExchange.Redis ProjectReference, previously commented out due
to a supposed AsciiHash conflict that no longer reproduces - the generated AsciiHash
classes are internal and this project has no InternalsVisibleTo access to them.
@mgravell
mgravell merged commit f7abfa6 into main Sep 10, 2026
4 checks passed
@mgravell
mgravell deleted the marc/execute-resp-benchmark branch September 10, 2026 12:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant