Unity — Memory Management with Pro Tips

Caner Nurdag
6 min readSep 6, 2022

--

Optimization has always been an important topic in game development process. As you may heard before, the most well-known optimization areas are CPU, GPU, memory, network etc.

In this article, I am going to explain how memory management works on Unity’s game development process and what can be done for better performance.

Before diving into Unity, let’s check how memory works when programming.

Operation system creates abstract version of physical memory for developers to be able to programe(You can find a simple visual for an example).

Source: https://i.stack.imgur.com/oew8U.png

In this abstraction, there are some virtual address spaces (VAS). (For further info: Link )

Two of them should be taken into considiration by Unity developers in order to get better performance: Heap and Stack.

Before going deeper into heap and stack, we need to understand which data structures are value type and which data structures are reference type. (Please refer to this link for further info: Link1, Link2)

Unity states that;

Types that are stored directly and copied during parameter passing are called value types. These include integers, floats, booleans and Unity’s struct types (eg, Color and Vector3).

Types that are allocated on the heap and then accessed via a pointer are called reference types, since the value stored in the variable merely “refers” to the real data. (Link)

In the light of information given regarding of value type and reference type, now we can compare stack and heap.

Stack

  • Store only value type
  • Store “references” of objects that are stored in Heap (This is related to Garbage collection)
  • Managed by the CPU. Variables are allocated when function is being created and freed when function is finished. This process is done automatically.
  • Faster than Heap
  • Limited capacity. If the capacity exceeds, stack overflow occurs.
  • LIFO structure

Heap

  • Store both reference type and value type (Please note that there is a common wrong information on the internet. Please refer to this link: Link)
  • Managed by memory manager or programmer
  • Can be fragmented.
  • Slower than Stack
  • Resizable capacity

Now, let’s take one more step and learn what is “garbage” and what is Garbage Collection.

Garbage: When usage of “reference” which is stored in stack is finished, CPU automatically free the “reference”. However, the object which the “reference” points is still stored in Heap. In this situation, this object is considered as “garbage”.

Garbage Collection

So, in order to solve this garbage problem, we have garbage collection systems. (For .Net environment’s garbage collection manager please refer to following link. Link )

In detail, this manager solves memory allocation and memory release problems for us.
According to Microsoft;

- Memory Allocation

When you initialize a new process, the runtime reserves a contiguous region of address space for the process. This reserved address space is called the managed heap. The managed heap maintains a pointer to the address where the next object in the heap will be allocated.

….

Allocating memory from the managed heap is faster than unmanaged memory allocation..

-Memory Release

the garbage collector creates a graph that contains all the objects that are reachable from the roots.

Objects that are not in the graph are unreachable from the application’s roots. The garbage collector considers unreachable objects garbage and releases the memory allocated for them.

Until now, we have learnt lots of general things about memory and .NET environment’s memory approach. So, how about in Unity? Let’s analyze Unity’s memory management system now.

Unity Memory Management System

Unity states that; (Link)

Unity uses three memory management layers to handle memory in your application:

- Managed memory: A controlled memory layer that uses a “managed heap” and a “garbage collector(GC)” to automatically allocate and assign memory.

Unlike .NET’s GC, this garbage collector is provided by active backend scripting such as Mono, IL2CPP.

- C# unmanaged memory: A layer of memory management that you can use in conjunction with the Unity Collections namespace and package. This memory type is called “unmanaged” because it doesn’t use a garbage collector to manage unused parts of memory.

- Native memory: C++ memory that Unity uses to run the engine. In most situations, this memory is inaccessible to Unity users, but it’s useful to be aware of it if you want to fine-tune certain aspects of the performance of your application.

Although this structure seems to be very complicated, don’t worry. It is easier to understand than it looks :)

At that point, there are two points we need to consider how they work: “Managed heap” and GC at first.

Managed Heap is automatically managed by backend scripting runtime. All objects that we create are allocated here. Let’s check the visual below that represent the managed heap.

Source: https://docs.unity3d.com/2020.1/Documentation/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html

As expected, when usage of a data is finished, GC destroys the data from managed heap.

Due to Unity’s GC algorithm, managed heap is not reordered after deleting a data.

Source: https://docs.unity3d.com/2020.1/Documentation/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html

Sometimes, the upcoming data is bigger than the empty block. So, now we have a problem :)

Source: https://docs.unity3d.com/2020.1/Documentation/Manual/BestPracticeUnderstandingPerformanceInUnity4-1.html

In order to solve this problem, managed heap manager has two operations

1- Run garbage collection process to find an appropriate empty space.

2- If operation fails, expand the heap size

As we understand how memory works, it is time to check best practices and examples.

Coding Tips

1- Use for loop instead of foreach loop
2- Reuse List/array wherever possible. Use may use Clear() method before reuse
3- Avoid “()=>” Anonymous methods
4- Avoid boxing and unboxing wherever possible
5- For string manipulation, use StringBuilder
6- Cache objects wherever possible
7- Object Pool for some objects like bullets etc.

Unity Tips

1- Use “Addressables System(Link)” to load and unload objects. There are some reasons for that.

First of all, everything would be more organized in big projects. You would be able to see what is going on easier.

Secondly, loading and unloading with “The Resources” class (Link) functions are heavier than you think and expensive. I think “Resources.UnloadUnusedAssets()” is not so appropriate for all cases due to not performant. So, this situation leads us to Addressables.

Extra Info: Objcets(such as prefabs) which are referenced on live objects’ components and not instantiated are loaded into memory as well. WTF :)

PS: Addressables has different purposes but i am not going to explain in this article.

2- Textures are objects that we need to consider the most in terms of memory.

  • Should be compressed
  • Size should be “Power of Two”(eg: 256 x 256)
  • Use “Atlas Texture” : I know artists does not want to spend more time for UV mapping, but this has a huge impace on memory and GPU performance

PS: Avoid using heavier shaders that have channels you do not use

As conclusion, memory management is not a point you can miss in game development. I strongly suggest that organize and plan your memory management approach in the beginning of your project.

--

--