Custom memory allocation in C# Part 12 — Hiding objects from GC

This is the twelfth part of the Custom memory allocation series. For your convenience you can find other parts in the table of contents in Part 1 — Allocating object on a stack

We already know how to allocate memory anywhere on the heap but if you think that it solves all problems with GC then most likely you are missing one thing. Even though we took control of allocation we still have GC algorithms running in the background. This means that GC will traverse all object graphs and try to clean them up. Pinning objects or allocating them in LOH doesn’t solve this issue. So, can we do something about it? Let’s see.

First, let’s see if we can measure “useless” GC runs. Let’s take this code:

We have a simple binary tree implementation with one integer field. Let’s allocate a chunk of memory and see how fast GC is:

Output:

According to Task Manager, whole graph consumes around 1.3 GB of memory. We can see that each full GC iteration takes almost 3 seconds on average.

So what can we do to make it faster? We need to convince GC to not traverse our objects. We need to replace every reference with integer. Interior references need to be replaced as well because they may leak into local variables so GC would have a way to enter the graph.

We will use UnsafeList class to make it easier.

First, we create new class:

Please forgive me ugliness of static fields here and there but this is just to make things easy to follow. Production-ready implementation should make it better, obviously. Also, I approximate size of the object to have 6 integers and allocate twice as long array as needed, just to make sure that I don’t hit wrong memory.

So we still have the field with value which we use for verification if our objects are still alive, but for pointers to children we use ordinary integers. Please keep in mind that I use Any CPU on Windows 7 x64, so the application runs as WoW64 and hence uses 32-bit long pointers.

When allocating children we add them to unsafe list. This creates objects on the heap which are then cleaned up by GC but whole list copies them and stores forever. You may want to use custom allocator instead, this is just an implementation detail.

Rest of the class is almost identical. We just need to extract pointers using Get method which looks like this:

So we create new reference on the fly as needed.

Now, let’s see the benchmark:

Yes, the only difference is in line 9 where I create different collection type. Results:

So we see that allocation is much faster (but we observed that in Part 2 as well). However, Full GC takes almost nothing! There is simply no objects to traverse so GC has nothing to do. At the same time, you can see that sum of integers is exactly the same before and after running GC so our objects work correctly.

Summary

Using custom allocation and manual memory management we can effectively turn GC off. There is one more caveat: our approach works with types we control. If we wanted to use it with built-int classes (or generally classes in which we cannot replace references with integers), we would need to implement custom wrapper which would extract all reference-type fields using reflection, nullify them and store addresses in arrays of integers. That would probably require some dynamic proxies as well so we will cover this another day.