BENCHMARKS · ALLOCATION & BUFFERS
new vs ArrayPool vs stackalloc — the GC pressure trade. Suite and raw reports: github.com/gdhami-net/dotnet-benchmarks.
ScratchBuffers
A 4 KB scratch buffer, 1,000 times: new array vs ArrayPool vs stackalloc.
TAKEAWAY Temporary buffers in hot paths: stackalloc for small fixed sizes, ArrayPool for anything bigger or variable. A fresh new byte[] per call is a GC tax with no upside.
In this run: ArrayPool_rent is fastest — 118.9× faster than the baseline.
In this run: ArrayPool_rent is fastest — 118.9× faster than the baseline.
RESULTS
| method | net9.0 | net10.0 | ratio | allocated | Δ net10.0 |
|---|---|---|---|---|---|
| New_array baseline | 240.9 µs ±2.0 µs | 263.6 µs ±38.5 µs | 1.00× | 3.93 MB | +9% |
| ArrayPool_rent | 2.2 µs ±1 ns | 2.2 µs ±13 ns | 0.01× | — | -0% |
| Stackalloc_span | 19.3 µs ±48 ns | 19.8 µs ±568 ns | 0.08× | — | +2% |
RATIO VS BASELINE · net10.0
THE LEDGER · SAME WORKLOAD ACROSS RELEASES (LOG)
New_arrayArrayPool_rentStackalloc_span
THE CODE BEING MEASURED
New_array — what this measures
new byte[4096] every time — simple, and 4 MB of garbage per thousand calls.
[Benchmark (Baseline)]
public int New_array()
{
var sum = 0;
for (var i = 0; i < 1_000; i++)
{
var buf = new byte[4096];
buf[0] = (byte)i;
sum += buf[0];
}
return sum;
}ArrayPool_rent — what this measures
ArrayPool rent/return — reuse instead of garbage.
[Benchmark]
public int ArrayPool_rent()
{
var pool = ArrayPool<byte>.Shared;
var sum = 0;
for (var i = 0; i < 1_000; i++)
{
var buf = pool.Rent(4096);
buf[0] = (byte)i;
sum += buf[0];
pool.Return(buf);
}
return sum;
}Stackalloc_span — what this measures
stackalloc inside a helper method — the frame (and the buffer) is reclaimed on every return. Never stackalloc directly in a loop: the stack only unwinds at method exit.
[Benchmark]
public int Stackalloc_span()
{
var sum = 0;
for (var i = 0; i < 1_000; i++) sum += UseScratch(i);
return sum;
}speed vs allocation
Fast is one axis. What it costs the GC is the other.
New_arrayArrayPool_rentStackalloc_span