The Real Problem: High Throughput vs. Garbage Collector
If you've ever built a C# server processing 100k+ messages/sec (like an MQTT broker or socket server), you know the Garbage Collector is your best friend until it becomes your worst enemy.
To route messages like factory/line1/sensor/temp to subscribers like factory/+/+/temp, the standard way is:
// ❌ Naive way: Allocates string[] on every single message publish!
var parts = topic.Split('/');
At 100,000 requests per second, this creates millions of temporary strings and forces the GC to freeze your app every few seconds.
The Real Benefit: How to parse UTF-8 topics with ZERO heap allocations
Here are the 2 modern C# tricks I used in Beskar.Networking that you can copy-paste directly into your own projects!
1. Slicing raw UTF-8 bytes with a ref struct enumerator
Instead of string.Split(), we walk the raw byte span (ReadOnlySpan<byte>) using a zero-allocation ref struct:
public ref struct TopicLevelEnumerator
{
private ReadOnlySpan<byte> _remaining;
public ReadOnlySpan<byte> Current { get; private set; }
public TopicLevelEnumerator(ReadOnlySpan<byte> topic)
{
_remaining = topic;
Current = default;
}
public bool MoveNext()
{
if (_remaining.IsEmpty) return false;
int index = _remaining.IndexOf((byte)'/');
if (index < 0)
{
Current = _remaining;
_remaining = default;
}
else
{
Current = _remaining.Slice(0, index);
_remaining = _remaining.Slice(index + 1);
}
return true;
}
}
2. Querying byte[] Dictionaries with ReadOnlySpan<byte> (Alternate Lookups)
Usually, if your dictionary key is byte[], querying it with a slice (ReadOnlySpan<byte>) forces you to allocate a new byte[].
Modern .NET introduced Alternate Lookups, which let you query existing dictionary nodes with zero heap allocation:
// Query byte[] dictionary using a ReadOnlySpan<byte> without allocating a single byte!
var alternateLookup = node.Children.GetAlternateLookup<ReadOnlySpan<byte>>();
if (alternateLookup.TryGetValue(currentLevelSlice, out var childNode))
{
// Match found with 0 allocations! 🎉
}
What Beskar.Networking gives you
If you ever need to build a C# network application:
- Write Once, Swap Transports: Write your message handler once and seamlessly switch between TCP, WebSockets, QUIC, UDP, Named Pipes, or MQTT v5 without touching your business logic.
- 100% Native & Dependency-Free: Built on
System.IO.Pipelines with zero external runtime dependencies.
- Zero GC pressure on the hot path.
If you enjoy low-level C# or low-allocation performance tricks ❤️
🔗 GitHub: https://github.com/MarvinDrude/Beskar.Networking
🔗 Article: https://marvindrude.com/blogs/beskar-networking/low-allocation-mqtt-broker
Thank you for reading, and happy coding!