Starving the Garbage Collector: A Pragmatic Guide to Zero-Allocation C# Developer Ian Cowley open-sourced a suite of high-performance, zero-dependency C# engines, including a native DataFrame library, a fast text searcher, and a semantic Markdown parser. The engines bypass the .NET Garbage Collector by using structs over classes and Span for zero-allocation memory management, achieving speeds rivaling native C/C++. Over the last few weeks, I’ve open-sourced a suite of high-performance, zero-dependency C engines. This includes a native DataFrame library Glacier.Polaris https://github.com/ian-cowley/Glacier.Polaris , a blistering fast text searcher Glacier.Grep https://github.com/ian-cowley/Glacier.Grep , and a semantic Markdown parser for RAG contexts Glacier.DocTree https://github.com/ian-cowley/Glacier.DocTree . You can find the source code for all of these on my GitHub https://github.com/ian-cowley?tab=repositories . A recurring question I’m getting from other devs looking at these repositories is simple: How exactly are you bypassing the Garbage Collector to get these speeds? I’ve never hidden my distaste for heavy, magic-filled frameworks. Whether it's an unwieldy data access library or a bloated client-side framework, they all share a common flaw: they wrap your code in layers of hidden allocations that murder your CPU caches and force the .NET Garbage Collector GC into overdrive. When you want to build systems that process millions of rows a second or rival native C/C++ in raw compute speed, you have to take control of your memory. To give you a fighting chance at writing your own high-performance engines, let's break down how memory allocation actually works in C , using the architecture of the Glacier repositories as our guide. In C , every time you use the new keyword on a class a reference type , you are asking the runtime to find a contiguous block of free memory on the Managed Heap. The heap is a messy place. When it gets full, the GC steps in. It pauses your application threads, traverses the object graph to see what you are still using, compacts the memory, and cleans up the garbage. For standard CRUD apps, this pause is negligible. For a DataFrame engine like Glacier.Polaris processing millions of rows, a GC pause is a catastrophic event. It's a heavy tax on your CPU cycles. The alternative is the Stack. The stack is a tightly managed, incredibly fast area of memory assigned exclusively to the current thread. When you create a struct a value type , it goes on the stack. When the method finishes, the stack unwinds, and the memory is instantly freed. No GC involved. Zero tax. But dropping classes for structs isn't just about dodging the GC; it's about mechanical sympathy. Modern CPUs don't read bytes from RAM one at a time; they pull 64-byte "cache lines." By using struct and explicitly packing your data via StructLayout LayoutKind.Sequential , you ensure that when the CPU grabs a cache line, it receives highly relevant, tightly packed data, drastically reducing cache misses. The Golden Rule: If you want to go fast in a tight loop, favor struct over class . Value types are great, but what about arrays and strings? Historically, if you wanted a subset of an array or a string, you called .Substring or .Skip .Take . These operations allocate new objects on the heap, copying the data over. If you look at the source for Glacier.DocTree or Glacier.Grep , you'll notice we rarely allocate new strings when reading text. Instead, we use Span