Introduction
When building high-frequency trading systems, every nanosecond matters.
In my open-source OpenHFT-Lab project, I’ve achieved sub-microsecond latencies by leveraging advanced .NET features that many developers overlook.
Today, I want to deep dive into one of the most powerful tools: the unmanaged
constraint.
The Problem: GC Pressure in Hot Paths
Traditional .NET applications work beautifully with reference types and managed memory.
However, when you need to process 50,000+ market data events per second with predictable latency, the Garbage Collector becomes your enemy.
// ❌ Traditional approach - creates GC pressure
public class TraditionalRingBuffer<T> where T : class
{
private readonly T[] _buffer;
public bool TryWrite(T item)
{
_buffer[_writeIndex] = item; // Reference assignment
// Each object allocation pressures GC
// Unpredictable pause times
// Memory fragmentation
}
}
Problems with this approach:
- 📈 GC Pressure: Every object creates garbage
- 🐌 Cache Misses: References scattered across memory
- ⏰ Unpredictable Latency: GC pauses can be 10ms+
- 💾 Memory Overhead: Object headers, alignment padding
The Solution: unmanaged
Constraint + Unsafe Code
The unmanaged
constraint ensures that your generic type contains no managed references, enabling direct memory manipulation:
public unsafe class LockFreeRingBuffer<T> where T : unmanaged
{
private readonly T* _buffer;
private readonly IntPtr _bufferPtr;
public LockFreeRingBuffer(int capacity)
{
// Allocate raw memory outside managed heap
int sizeInBytes = capacity * sizeof(T);
_bufferPtr = Marshal.AllocHGlobal(sizeInBytes);
_buffer = (T*)_bufferPtr.ToPointer();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryWrite(in T item)
{
// Direct memory write - zero allocations
_buffer[writeIndex & _mask] = item;
// Memory barrier for thread safety
Thread.MemoryBarrier();
return true;
}
}
What Qualifies as unmanaged
?
The compiler enforces strict rules:
✅ Allowed Types:
// Primitives
int, long, byte, bool, decimal, double, float
// Enums
public enum Side : byte { Buy = 1, Sell = 2 }
// Structs with only unmanaged fields
public readonly struct MarketDataEvent
{
public readonly long Timestamp;
public readonly long PriceTicks;
public readonly Side Side;
public readonly int SymbolId;
}
// Pointers (unsafe context)
int*, T* where T : unmanaged
❌ Forbidden Types:
// Reference types
string, object, classes, interfaces
// Arrays (they’re references)
int[], T[]
// Generic collections
List<T>, Dictionary<K,V>
// Structs with references
public struct BadStruct
{
public int Value; // ✅ OK
public string Name; // ❌ FAILS
}
Real-World Example: Market Data Event
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public readonly struct MarketDataEvent
{
public readonly long Sequence;
public readonly long Timestamp;
public readonly Side Side;
public readonly long PriceTicks;
public readonly long Quantity;
public readonly EventKind Kind;
public readonly int SymbolId;
// Total size: 56 bytes
// Cache-friendly, zero padding waste
}
Design decisions:
- 📏 Fixed-point arithmetic:
long
for price instead ofdecimal
- 🏷 Enums instead of strings
- 🆔 Integer IDs instead of string names
- 📦 Pack = 1 to eliminate padding
Performance Benchmarks
Metric | Traditional | unmanaged |
Improvement |
---|---|---|---|
Latency (P50) | 200μs | 20μs | 10x |
Latency (P99) | 2ms | 80μs | 25x |
Throughput | 500K/s | 50M/s | 100x |
Memory Alloc | 50MB/s | 0MB/s | Zero GC |
CPU Usage | 80% | 15% | 5x |
Memory Layout Optimization
[StructLayout(LayoutKind.Explicit, Size = 64)]
public struct CacheAlignedEvent
{
[FieldOffset(0)] public readonly long Timestamp;
[FieldOffset(8)] public readonly long Price;
[FieldOffset(16)] public readonly long Quantity;
[FieldOffset(24)] public readonly int SymbolId;
[FieldOffset(28)] public readonly Side Side;
// Padding to 64 bytes
}
Benefits:
- 🚀 Cache efficiency
- 🎯 Prevents false sharing
- 📏 Predictable layout
Thread Safety with Memory Barriers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryRead(out T item)
{
long currentRead = Volatile.Read(ref _readIndex);
long currentWrite = Volatile.Read(ref _writeIndex);
if (currentRead >= currentWrite)
{
item = default;
return false;
}
item = _buffer[currentRead & _mask];
Thread.MemoryBarrier();
Volatile.Write(ref _readIndex, currentRead + 1);
return true;
}
When NOT to Use unmanaged
❌ Don’t use if:
- You need string manipulation
- Polymorphism is important
- Maintainability > raw performance
- You must interop with APIs expecting references
✅ Perfect for:
- High-frequency trading
- Game engines
- Real-time audio/video
- IoT & embedded systems
- Scientific computing
Complete Working Example
public unsafe class SimpleLockFreeRingBuffer<T> where T : unmanaged
{
private readonly T* _buffer;
private readonly int _capacity;
private readonly int _mask;
private long _writeIndex;
private long _readIndex;
private readonly IntPtr _bufferPtr;
public SimpleLockFreeRingBuffer(int capacity)
{
if ((capacity & (capacity - 1)) != 0)
throw new ArgumentException("Capacity must be power of 2");
_capacity = capacity;
_mask = capacity - 1;
int sizeInBytes = capacity * sizeof(T);
_bufferPtr = Marshal.AllocHGlobal(sizeInBytes);
_buffer = (T*)_bufferPtr.ToPointer();
}
public bool TryWrite(in T item)
{
long write = Volatile.Read(ref _writeIndex);
long read = Volatile.Read(ref _readIndex);
if (write - read >= _capacity) return false;
_buffer[write & _mask] = item;
Thread.MemoryBarrier();
Volatile.Write(ref _writeIndex, write + 1);
return true;
}
public bool TryRead(out T item)
{
long read = Volatile.Read(ref _readIndex);
long write = Volatile.Read(ref _writeIndex);
if (read >= write)
{
item = default;
return false;
}
item = _buffer[read & _mask];
Thread.MemoryBarrier();
Volatile.Write(ref _readIndex, read + 1);
return true;
}
~SimpleLockFreeRingBuffer() => Marshal.FreeHGlobal(_bufferPtr);
}
Conclusion
The unmanaged
constraint is one of .NET’s most powerful yet underused features.
When you need predictable, sub-microsecond performance, it can mean the difference between fast enough and world-class.
Key takeaways:
- 🎯 Use in hot paths where every nanosecond counts
- 📦 Design structs without managed references
- 🚀 Combine with unsafe code for maximum performance
- ⚡ Ideal for financial, gaming, and real-time systems
📌 Full source code: OpenHFT-Lab on GitHub
Do you want me to also prepare a shortened LinkedIn-friendly version so you can cross-post it? That would help drive traffic to your GitHub.
Top comments (0)