DEV Community

Cover image for High-Throughput Zero-Allocation Pipelines in .NET 9: Span<T>, MemoryPool, and Channels
Ama Senevirathne
Ama Senevirathne

Posted on

High-Throughput Zero-Allocation Pipelines in .NET 9: Span<T>, MemoryPool, and Channels

High-Throughput Zero-Allocation Pipelines in .NET 9: Span, MemoryPool, and Channels

Market & Architectural Context: Fintech and high-load telemetry architectures in .NET 9 are eliminating Gen0/Gen1 GC pauses to achieve sub-millisecond p99 latency at 500k+ msg/sec.

Figure 1: .NET 9 High-Throughput Zero-Allocation Pipeline Topology

Figure 1: .NET 9 High-Throughput Zero-Allocation Pipeline Topology


In high-frequency financial trading, real-time telemetry, and microservices ingesting millions of requests per minute, Garbage Collection (GC) pauses are the primary cause of tail-latency spikes. Even brief Gen2 collections can push p99 latency from under 1ms to over 250ms.

In this deep dive, we architect a zero-allocation ingestion pipeline in .NET 9 capable of processing 500,000+ messages per second on commodity hardware.


Architecture & Interview Cheat Sheet

Feature Allocation Profile Thread Safety Optimal Use Case
string.Substring() Allocates new string on heap Thread-safe (immutable) Legacy parsing (avoid in hot path)
ReadOnlySpan<char> 0 bytes (stack-only ref struct) Single-thread stack only In-memory tokenization & string parsing
Memory<T> Heap object, views slice Thread-safe across async/await Asynchronous socket pipelines
ArrayPool<T>.Shared 0 bytes (reused buffer array) Thread-safe rental Buffering socket read streams
Channel<T>.CreateBounded Minimal fixed queue buffer Lock-free thread-safe High-throughput producer-consumer

1: Slicing Sockets Without Heap Allocations

Instead of creating strings from socket buffers, modern C# utilizes ReadOnlySpan<byte> and Utf8Parser:

using System;
using System.Buffers;
using System.Buffers.Text;
using System.Text;

public ref struct FastHeaderParser
{
    private readonly ReadOnlySpan<byte> _buffer;

    public FastHeaderParser(ReadOnlySpan<byte> buffer)
    {
        _buffer = buffer;
    }

    public bool TryExtractCorrelationId(out Guid correlationId)
    {
        // Zero-copy search for header delimiter
        int index = _buffer.IndexOf((byte)':');
        if (index < 0)
        {
            correlationId = default;
            return false;
        }

        ReadOnlySpan<byte> idSlice = _buffer.Slice(index + 1).Trim();
        return Utf8Parser.TryParse(idSlice, out correlationId, out _);
    }
}
Enter fullscreen mode Exit fullscreen mode

2: Lock-Free Ingestion with System.Threading.Channels

A Channel<T> provides high-performance, lock-free communication between ingestion endpoints and worker threads:

using System;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;

public sealed class IngestionEngine<T>
{
    private readonly Channel<T> _channel;

    public IngestionEngine(int capacity = 50_000)
    {
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait,
            SingleWriter = false,
            SingleReader = false
        };
        _channel = Channel.CreateBounded<T>(options);
    }

    public ValueTask PublishAsync(T message, CancellationToken ct = default)
    {
        return _channel.Writer.WriteAsync(message, ct);
    }

    public async Task StartConsumerAsync(Func<T, ValueTask> processor, CancellationToken ct)
    {
        var reader = _channel.Reader;
        while (await reader.WaitToReadAsync(ct).ConfigureAwait(false))
        {
            while (reader.TryRead(out var item))
            {
                await processor(item).ConfigureAwait(false);
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

3: Reusable Buffers with ArrayPool

Never instantiate new byte[4096] inside an HTTP middleware loop:

using System;
using System.Buffers;
using System.IO;
using System.Threading.Tasks;

public async ValueTask ProcessStreamZeroAllocAsync(Stream stream)
{
    byte[] rentBuffer = ArrayPool<byte>.Shared.Rent(8192);
    try
    {
        int bytesRead = await stream.ReadAsync(rentBuffer.AsMemory(0, 8192));
        ReadOnlySpan<byte> activeSlice = rentBuffer.AsSpan(0, bytesRead);

        // Execute zero-allocation domain parsing
        ProcessSlice(activeSlice);
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(rentBuffer);
    }
}

private static void ProcessSlice(ReadOnlySpan<byte> slice)
{
    // Zero heap allocations in hot path
}
Enter fullscreen mode Exit fullscreen mode

4: Senior .NET Engineering Invariants

  1. GC Server Mode: Enable <ServerGarbageCollection>true</ServerGarbageCollection> for multi-core server nodes.
  2. ValueTask<T> Over Task<T>: Use ValueTask for methods that frequently complete synchronously to prevent heap Task allocations.
  3. Prefer in and ref readonly: Avoid large struct copies across function calls.

Production Implementations & GitHub Repositories

Explore the production open-source architectures and working implementations on GitHub:

  • GitHub Profile: github.com/amasen02
  • Production Repositories:
    • any-db-mcp - Universal Model Context Protocol (MCP) bridge for dynamic database inspection and tool-calling.
    • centaurloop - Autonomous agentic loop framework featuring deterministic compiler gating and AST verification.
    • agent-barn - Multi-agent fleet orchestration system with isolated sandboxing and shared context memory.
    • ConcurrentCache - High-throughput, zero-allocation concurrent cache engineered in modern C# / .NET.
    • credscan - High-performance AST security auditor and credential leakage detector.

Technical Author

Ama Senevirathne is a Senior Full-Stack & AI Systems Engineer architecting enterprise software across Autonomous Agent Infrastructure, Distributed Systems, High-Performance .NET 9 / C#, and Zoneless Angular Signals.

Top comments (0)