BENCHMARKS · ENCODING & BUFFERS
UTF-8 and Base64: allocating paths vs buffer reuse. Suite and raw reports: github.com/gdhami-net/dotnet-benchmarks.
Utf8Encode
UTF-8 encode 5,000 strings: allocating GetBytes vs encoding into a rented buffer.
TAKEAWAY On hot paths that feed sockets or files, encode into a rented or pooled buffer — the bytes were always going to be copied somewhere anyway; the throwaway array in the middle is pure GC pressure.
In this run: GetBytes_rented is fastest — 3.7× faster than the baseline.
In this run: GetBytes_rented is fastest — 3.7× faster than the baseline.
RESULTS
| method | net9.0 | net10.0 | ratio | allocated | Δ net10.0 |
|---|---|---|---|---|---|
| GetBytes_alloc baseline | 77.3 µs ±7.7 µs | 62.2 µs ±851 ns | 1.00× | 351.6 KB | -20% |
| GetBytes_rented | 18.4 µs ±166 ns | 16.8 µs ±135 ns | 0.27× | — | -9% |
RATIO VS BASELINE · net10.0
THE LEDGER · SAME WORKLOAD ACROSS RELEASES (LOG)
The ledger starts here — 3 run recorded. Trend lines appear from the second release onward.
THE CODE BEING MEASURED
GetBytes_alloc — what this measures
Encoding.UTF8.GetBytes — a fresh byte[] per string.
[Benchmark (Baseline)]
public int GetBytes_alloc()
{
var total = 0;
foreach (var s in _lines) total += Encoding.UTF8.GetBytes(s).Length;
return total;
}GetBytes_rented — what this measures
Encode into one rented buffer — reuse instead of allocate.
[Benchmark]
public int GetBytes_rented()
{
var buf = ArrayPool<byte>.Shared.Rent(1024);
var total = 0;
foreach (var s in _lines) total += Encoding.UTF8.GetBytes(s, 0, s.Length, buf, 0);
ArrayPool<byte>.Shared.Return(buf);
return total;
}Base64Encode
Base64-encode a 64 KB payload: Convert.ToBase64String vs Base64.EncodeToUtf8 into a reusable buffer.
TAKEAWAY Convert.ToBase64String makes a big UTF-16 string you usually re-encode right back to bytes. When the destination is bytes (HTTP, files), Base64.EncodeToUtf8 skips the string entirely.
In this run: EncodeToUtf8_buffer is fastest — 24.0× faster than the baseline.
In this run: EncodeToUtf8_buffer is fastest — 24.0× faster than the baseline.
RESULTS
| method | net9.0 | net10.0 | ratio | allocated | Δ net10.0 |
|---|---|---|---|---|---|
| ToBase64String baseline | 69.1 µs ±1.3 µs | 61.5 µs ±1.6 µs | 1.00× | 170.7 KB | -11% |
| EncodeToUtf8_buffer | 2.2 µs ±333 ns | 2.6 µs ±165 ns | 0.04× | — | +19% |
RATIO VS BASELINE · net10.0
THE LEDGER · SAME WORKLOAD ACROSS RELEASES (LOG)
The ledger starts here — 3 run recorded. Trend lines appear from the second release onward.
THE CODE BEING MEASURED
ToBase64String — what this measures
Convert.ToBase64String — one large string allocation.
[Benchmark (Baseline)]
public int ToBase64String()
=> Convert.ToBase64String(_payload).Length;EncodeToUtf8_buffer — what this measures
Base64.EncodeToUtf8 into a preallocated byte buffer.
[Benchmark]
public int EncodeToUtf8_buffer()
{
Base64.EncodeToUtf8(_payload, _dest, out _, out var written);
return written;
}speed vs allocation
Fast is one axis. What it costs the GC is the other.
GetBytes_allocGetBytes_rentedToBase64StringEncodeToUtf8_buffer