How to get allocations in .NET? And how big is an empty array?

20/11/2022
.NETC#

Often times we hear about allocations on the heap. How can we easily measure this? This article will show you a very easy way of doing so.

Plus we will answer the question how big is an empty array? And if you think 0 bytes, then spoiler alert, that is not the case at all.

Getting allocations of the current thread

.NET itself brings a very convenient helper with the GC-class. GC is short for Garbage Collector, as he manages all of your heap allocations in the first place. If that does not ring a bell for you, here is a small overview how the GC works: The garbage collector in .NET.

So we can write code like this to measure our allocations:

var before = GC.GetAllocatedBytesForCurrentThread();

var list = new int[0];
var after = GC.GetAllocatedBytesForCurrentThread();

Console.WriteLine($"Used bytes: {after - before}");

Which would print Used bytes: 24 on my local machine (.NET7 MacBook M1 Pro). Hupps? Why does an empty array have some allocations? The reason is simple, each of your arrays is of type Array and Array has some fields to hold some information. Every type in .net has some information, which is present in its header. Don't worry you don't have to worry about that stuff in your daily work. For once we have the method table and we also have length field. Well, that field is part of Array itself, the base type from which every other array derives. And that makes all in all 24 bytes. Sharplab.io has a nice way of showing you such information.

Memory Profile

Thanks again to @TimothyMakkison for pointing that out. Originally I, falsely, claimed that there are some other properties occupying the space (see the discussion thread down below).

Pro-Tip: If you are using Array<T>.Empty then you will re-use the same static instance everywhere. That means you are only allocating once 24 bytes and not over and over again. Plus it is more readable.

GC.GetAllocatedBytesForCurrentThread() will return the cumulative total of bytes allocated by the current thread. So even if the GC would have run, this will show how many allocations are done in total. That can be good or bad.

You can simulate such behavior, where you don't want to take the garbage collector into account with the following code:

GC.TryStartNoGCRegion(1_000_000);
// Your code
GC.EndNoGCRegion();

GC.TryStartNoGCRegion(1_000_000); will enter a region where the GC will try not to run. You have to pass in a size, you can allocate before it performs a GC cycle. You can only enter "one" region. So nested calls will result in an exception. It is also critical to call GC.EndNoGCRegion(); at the end. So normally you would wrap your calls in a try-catch fashion, where you would call GC.EndNoGCRegion in the final part.

2
An error has occurred. This application may no longer respond until reloaded. Reload x