Skip to main content

Benchmarks

Every measured warm Reservoir path allocated 0 B per operation.

Results below used BenchmarkDotNet 0.15.8 MediumRun, .NET 10.0.11, Windows 11, AMD EPYC 9V74. Nanosecond timings vary by machine; compare methods within a table.

Automated results

Generated 2026-08-11 23:33 UTC from commit d32ff7f69c75. See the GitHub Actions run for logs and downloadable artifacts.

Core pool

The payload owns a 256-byte buffer. Lower ratio is better; new is the baseline.

MethodMeanRatioAllocated
new11.83 ns1.00304 B
Reservoir10.36 ns0.880 B
Microsoft.Extensions.ObjectPool11.57 ns0.980 B
ConcurrentBag<T> pool25.19 ns2.130 B

Warm allocation guarantee

PoolMeanAllocated
ObjectPool10.29 ns0 B
ListPool10.97 ns0 B
DictionaryPool11.90 ns0 B
HashSetPool12.01 ns0 B
QueuePool11.32 ns0 B
StackPool11.14 ns0 B
StringBuilderPool15.91 ns0 B

Specialized workloads

WorkloadBaselineReservoirBaseline allocatedReservoir allocated
StringBuilder, append 128 chars29.67 ns20.82 ns400 B0 B
List<int>, 8 items17.76 ns25.01 ns88 B0 B
List<int>, 128 items196.35 ns184.15 ns568 B0 B
List<int>, 2,048 items3,036.25 ns2,750.70 ns8,248 B0 B

The single-thread TLS StringBuilder cache measured 10.35 ns and 0 B; it gives up cross-thread reuse and bounded shared capacity.

Choosing a rental API

For synchronous hot paths on .NET 10, prefer RentScoped(out T). On .NET 8, manual Rent() and Return() remain faster. Both warm paths allocate 0 B. Always use manual rental when ownership crosses an await, and measure representative workloads on target hardware.

Reproduce

dotnet run -c Release -f net10.0 --project benchmarks/Reservoir.Benchmarks -- --filter "*" --job Short --runtimes net8.0 net10.0 --apples

Raw Markdown, CSV, and HTML exports—including 1–32 worker contention results—are available from the GitHub Actions run.