Unity’s garbage collector is super slow and the bane of our programming life. It’s the reason we can’t use foreach, have to make pools of objects, and go to great lengths to avoid boxing. It’s also seemingly mandatory, but that’s not quite true. Today’s article shows you a way that you can skip the GC and still allocate memory!

To recap, C# has reference types and value types. Reference types are class instances and value types are primitives like int and structs. Reference types are created on the heap, reference counted, and garbage collected. Value types are created on the stack unless they’re fields of classes.

So it’s good practice in Unity to avoid using a lot of reference types because they ultimately create garbage that the garbage collector has to collect, which fragments memory and creates frame spikes. So we turn to structs instead of classes because they don’t create garbage when we use them as local variables. Think of Vector3, Quaternion, or Color.

We can use these structs as local variables and pass them as parameters with or without the ref keyword. But those are temporary operations. Sometimes we’d like to keep them longer, so we add a struct field to a class or we create an array or List of structs. Unfortunately, that class or array or List is itself going to become garbage someday. And so it seems we’re painted into a corner and must accept the inevitability of the GC.

Thankfully, we have a way out! There is a way to allocate memory without using the new operator and without using any classes, including arrays. The way to do it is to call System.Runtime.InteropServices.Marshal.AllocHGlobal. You pass it the number of bytes you’d like to allocate and it allocates them on the heap and returns you an IntPtr struct. When you’re done, call Marshal.FreeHGlobal and pass it the IntPtr you got back from AllocHGlobal.

In the meantime, you can do whatever you want with that IntPtr. You’ll probably want to turn on “unsafe” code by putting -unsafe in your Asset’s directory’s gmcs.rsp, smcs.rsp, and mcs.rsp. Then you can cast the IntPtr to a pointer and access the memory however you want. Here’s a little example where we store a struct on the heap:

using System;
using System.Runtime.InteropServices;
using UnityEngine;
 
struct MyStruct
{
	public int Int;
	public bool Bool;
	public long Long;
}
 
unsafe void Foo()
{
	// Allocate enough room for one MyStruct instance
	// Cast the IntPtr to MyStruct* so we can treat the memory as a MyStruct
	var pStruct = (MyStruct*)Marshal.AllocHGlobal(sizeof(MyStruct));
 
	// Store a struct on the heap!
	*pStruct = new MyStruct { Int = 123, Bool = true, Long = 456 };
 
	// Read the struct from the heap
	Debug.Log(pStruct->Int + ", " + pStruct->Bool + ", " + pStruct->Long);
 
	// Free the heap memory when we're done with it
	Marshal.FreeHGlobal((IntPtr)pStruct);
}

Other than the Debug.Log, this code doesn’t create any garbage. We can store the MyStruct* long-term just like a reference to a class. Copying a pointer is cheap, too. A MyStruct* is just 4 or 8 bytes regardless of how big MyStruct gets.

Of course we can do whatever else we want with the heap memory we get back from AllocHGlobal. Want to replace arrays? Easy!

using System;
using System.Runtime.InteropServices;
using UnityEngine;
 
/// <summary>
/// An array stored in the unmanaged heap
/// http://JacksonDunstan.com/articles/3740
/// </summary>
unsafe struct UnmanagedArray
{
	/// <summary>
	/// Number of elements in the array
	/// </summary>
	public int Length;
 
	/// <summary>
	/// The size of one element of the array in bytes
	/// </summary>
	public int ElementSize;
 
	/// <summary>
	/// Pointer to the unmanaged heap memory the array is stored in
	/// </summary>
	public void* Memory;
 
	/// <summary>
	/// Create the array. Its elements are initially undefined.
	/// </summary>
	/// <param name="length">Number of elements in the array</param>
	/// <param name="elementSize">The size of one element of the array in bytes</param>
	public UnmanagedArray(int length, int elementSize)
	{
		Memory = (void*)Marshal.AllocHGlobal(length * elementSize);
		Length = length;
		ElementSize = elementSize;
	}
 
	/// <summary>
	/// Get a pointer to an element in the array
	/// </summary>
	/// <param name="index">Index of the element to get a pointer to</param>
	public void* this[int index]
	{
		get
		{
			return ((byte*)Memory) + ElementSize * index;
		}
	}
 
	/// <summary>
	/// Free the unmanaged heap memory where the array is stored, set <see cref="Memory"/> to null,
	/// and <see cref="Length"/> to zero.
	/// </summary>
	public void Destroy()
	{
		Marshal.FreeHGlobal((IntPtr)Memory);
		Memory = null;
		Length = 0;
	}
}
 
unsafe void Foo()
{
	// Create an array of 5 MyStruct instances
	var array = new UnmanagedArray(5, sizeof(MyStruct));
 
	// Fill the array
	for (var i = 0; i < array.Length; ++i)
	{
		*((MyStruct*)array[i]) = new MyStruct { Int = i, Bool = i%2==0, Long = i*10 };
	}
 
	// Read from the array
	for (var i = 0; i < array.Length; ++i)
	{
		var pStruct = (MyStruct*)array[i];
		Debug.Log(pStruct->Int + ", " + pStruct->Bool + ", " + pStruct->Long);
	}
 
	// Free the array's memory when we're done with it
	array.Destroy();

One downside of this approach is that we need to make sure to call FreeHGlobal or the memory will never be released. The OS will free it all for you when the app exits. One issue crops up when running in the editor because the app is the editor, not your game. So clicking the Play button to stop the game means you might leave 100 MB of memory un-freed. Do that ten times and the editor will be using an extra gig of RAM! You could just reboot the editor, but there’s a cleaner way so you don’t even need to do that.

Instead of calling AllocHGlobal and FreeHGlobal directly, you can insert a middle-man who remembers all of the allocations you’ve done. Then you can tell this middle-man to free them all when your app exits. Again, this is only necessary in the editor so it’s good to use #if and [Conditional] to strip out as much of the middle-man as possible from your game builds.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
 
#if UNITY_EDITOR
using System.Collections.Generic;
#endif
 
/// <summary>
/// Allocates and frees blocks of unmanaged memory. Tracks allocations in the Unity editor so they
/// can be freed in bulk via <see cref="Cleanup"/>.
/// http://JacksonDunstan.com/articles/3740
/// </summary>
public static class UnmanagedMemory
{
	/// <summary>
	/// Keep track of all the allocations that haven't been freed
	/// </summary>
	#if UNITY_EDITOR
	private static readonly HashSet<IntPtr> allocations = new HashSet<IntPtr>();
	#endif
 
	/// <summary>
	/// Allocate unmanaged heap memory and track it
	/// </summary>
	/// <param name="size">Number of bytes of unmanaged heap memory to allocate</param>
	public static IntPtr Alloc(int size)
	{
		var ptr = Marshal.AllocHGlobal(size);
		#if UNITY_EDITOR
			allocations.Add(ptr);
		#endif
		return ptr;
	}
 
	/// <summary>
	/// Free unmanaged heap memory and stop tracking it
	/// </summary>
	/// <param name="ptr">Pointer to the unmanaged heap memory to free</param>
	public static void Free(IntPtr ptr)
	{
		Marshal.FreeHGlobal(ptr);
		#if UNITY_EDITOR
			allocations.Remove(ptr);
		#endif
	}
 
	/// <summary>
	/// Free all unmanaged heap memory allocated with <see cref="Alloc"/>
	/// </summary>
	[Conditional("UNITY_EDITOR")]
	public static void Cleanup()
	{
		foreach (var ptr in allocations)
		{
			Marshal.FreeHGlobal(ptr);
		}
		allocations.Clear();
	}
}

Now just replace your calls to Marshal.AllocHGlobal and Marshal.FreeHGlobal with calls to UnmanagedMemory.Alloc and UnmanagedMemory.Free. If you want to use the UnmanagedArray struct above, you might want to do this replacement in there as well. The final step is to put this one-liner in any MonoBehaviour.OnApplicationQuit:

UnmanagedMemory.Cleanup();

Hopefully this technique will be a good addition to your toolbox as a Unity programmer. It can certainly come in handy when you need to avoid the GC!