Back To Home

Optimization

22-03-2024


Squeeze More Out of Less: Memory Optimization Techniques for Unity Games

"Have you ever poured your heart and soul into crafting a stunning Unity game, only to see it brought to its knees by performance issues? Lag, stuttering, and crashes can leave players frustrated and abandon your masterpiece before they even experience its glory. But fear not, fellow developers! With some clever memory optimization techniques, you can squeeze more performance out of even the most resource-intensive Unity games."

post 2

Why Memory Optimization Matters :-

Memory is a precious resource for any game, especially on mobile devices with limited RAM. When your game consumes too much memory, it can lead to:

  1. Lag :
    Frame rates drop, causing jerky and unresponsive gameplay.

  2. Stuttering :
    The game struggles to keep up, resulting in choppy visuals and audio.

  3. Crashing :
    The system runs out of memory, forcing the game to shut down unexpectedly.

By optimizing your memory usage, you can ensure your game runs smoothly and delivers a delightful experience for your players.


The Toy Box Analogy: Understanding Memory Management :-

  1. Imagine you have a toy box, and you want to keep your toys organized. Memory management in C# is like keeping your toys in the right place and making sure you don’t lose any of them.

  2. In a computer program, there are lots of different things called “objects” that the program uses to do its work. These objects are like toys, and they need a place to stay in the computer’s memory.

  3. Just like you have a limited amount of space in your toy box, a computer also has a limited amount of memory. So, memory management is the process of making sure all the objects have enough space to stay and keeping track of where they are.

  4. One important job of memory management is to decide when an object is no longer needed and can be removed from memory, just like when you decide you don’t want to play with a toy anymore and put it back in the toy box.

  5. To do this, the computer keeps track of which objects are being used and which ones are not. If an object is no longer needed, the memory manager can free up the space it was using so that other objects can use it.

  6. But sometimes, the computer might forget to clean up the memory, just like when you forget to put your toys back in the toy box. This can lead to a problem called a “memory leak” where the computer keeps using more and more memory even though it’s not needed.

  7. To avoid memory leaks, C# has a special feature called “garbage collection.” It’s like having a magical helper who looks at all the objects in memory and cleans up the ones that are not being used anymore. This way, the memory stays organized and doesn’t get filled up unnecessarily.

  8. So, memory management in C# is all about keeping track of objects, making sure they have enough space, and cleaning up the memory when objects are no longer needed. Just like organizing your toys and making sure your toy box doesn’t overflow!

  9. We should always use memory in an efficient manner. Unnecessary memory allocations lead to poor user experience due to excessive garbage collection (costing precious CPU time) and memory leaks, which will lead to crashes. None of these situations are acceptable in modern game releases.

Manual vs. Automatic: Memory Management Approaches :-

Memory management is like a complete package which deals with the following :

1. Allocating the Memory.

2. Tracking life time of objects.

3. Deallocating the Memory.

  1. All of these steps can be done either manually like in c, c++ or automatically like in c#. In c# we can not control allocation, tracking and deallocation of memory, it is the job of Garbage Collection (GC). GC handles the memory management of all the managed objects, it doesn’t deal with unmanaged objects. In unity it is managed by Mono or IL2CPP engine.

  2. Lets see managed and unmanaged (code and objects). Managed code is the code that is written inside .NET framework and executed by CLR and any code that is written outside and executed by OS is unmanaged code.

  3. Managed objects are the objects that are created and managed under the scope of CLR, .NET framework. classes such as string, int, bool variables are referred to as managed code. Unmanaged Objects are the objects created outside of .NET framework and not managed by CLR. eg. FIle Stream, connection objects etc.

post 2

Stack And Heap :-

  1. Managed memory is divided into two types of memory Stack and heap. In C# there are two places where an object can be stored — the stack and the heap. The first one is ordered, fast but limited. The second is random, large and slower, while objects allocated on the heap can be accessed from anywhere. Objects allocated on the stack are available only inside of a stack frame (execution of a method). It handles local variables and handles the loading and unloading of functions as they’re called. while objects allocated on the heap can be accessed from anywhere.

  2. The total stack size is usually very small, usually on the order of MB. It’s possible to cause a stack overflow by allocating more space than the stack can support. This can occur during exceptionally large call stacks (for example, an infinite loop) or having a large number of local variables, but in most cases, causing a stack overflow is rarely a concern despite its relatively small size.

  3. Heap is one big chunk of memory which is allocated one for program, which can dynamically increase or decrease as per requirement. Memory management in heap is automated by GC. During the initialization of our Unity app, the Mono platform will request a given chunk of memory from the OS and use it to generate a heap memory space that our C# code can use (often known as the managed heap). This heap space starts off fairly small, less than 1 MB, but will grow as new blocks of memory are needed by our script code. This space can also shrink by releasing it back to the OS if Unity determines that it’s no longer needed.

  4. Its not always true that reference types are stored in heap and value types are in stack. It depends on where we are declaring the variables. We can store values types in heap when we declare it as a member of a class. eg. class Ref{ int num =123; }, here num is value type but it is a member variable of class so it will store in heap. If I use struct instead of class then this num variable will be stored in stack. If the value type was declared as a method parameter or inside the method then it’s stored on the stack.

  5. Since every type in C# derives from System.Object, value types can be assigned to variables or passed to methods that expect an object. In such cases, the value is copied and stored on the heap wrapped as a reference type, in an operation known as boxing.

Garbage Collector :-

  1. A garbage collector operates on heap memory. It prevent us from memory leak by keeping track of which memory is still in use then releasing blocks when they become unused. GC in Unity pauses the execution of all your program code until it has finished all its work, causing a CPU spike which can lead to sudden frame drop. We don’t want any sudden frame drop in our game do we?

  2. We can call GC through the code or implicitly by the memory manager whenever the system is running low on memory. It doesn’t have a fixed time to be called. It’s not in our control. So we should carefully play with memory and let system call for GC as less as possible. You can also disable GC at runtime and manage memory things by yourself.

Mono or IL2CPP :-

  1. Mono is an open-source implementation of Microsoft’s . NET Framework. The goal of the Mono project is to provide cross-platform development through a framework that allows code written in a common programming language to run against many different hardware platforms, including Linux, macOS, Windows, ARM, PowerPC, and more. Unity Technologies built a native C++ backend for the sake of speed and allowed its users control of this game engine through Mono as a scripting interface.

  2. IL2CPP is a scripting backend designed to convert Mono’s CIL output directly into the native C++ code. This leads to improved performance since the application will now be running native code.IL2CPP provides its own AOT compiler and VM, allowing custom improvements to subsystems such as the GC and compilation process.

We should choose IL2CPP over mono for further development because :

1. It generates more improved code than mono.

2. It supports AOT compiler.

3. We can use engine code stripping to reduce the code size.

4. It provides more code security than mono.

Profiling memory :-

  1. The best metric we can use to measure the health of our memory management is simply watching the behavior of the GC. The more work it’s doing, the more waste we’re generating and the worse our application’s performance is likely to become.

  2. We can use both the CPU Usage Area and Memory Area of the Profiler window to observe the amount of work the GC is doing and the time it is taking to do it.

Object pooling :-

  1. Unity allows you to create object pools, which are pre-allocated instances of objects that can be reused instead of creating new objects every time. This helps reduce memory allocation and deallocation overhead..

Proper Use of Resources :-

  1. Unity provides various resources such as textures, audio clips, and meshes. It’s important to load and unload these resources efficiently, especially when they are no longer needed, to avoid unnecessary memory consumption.

Destroy :-

  1. Unity has oneDestroy(), that can be used to release resources held by objects.Destroy() is used for releasing Unity-specific objects, like GameObjects. Properly disposing or destroying objects when they are no longer needed helps free up memory.

AssetBundles :-

  1. Unity allows you to create AssetBundles, which are external files that contain assets like models, textures, or audio clips. By using AssetBundles, you can load assets on demand, reducing the initial memory footprint of your game.

Texture Compression :-

  1. Textures consume a significant amount of memory in games. Unity provides various texture compression options that can reduce their memory footprint without significant loss in visual quality. Choosing the appropriate compression format can help optimize memory usage.

Remember :-

  1. Optimization is an ongoing process. Regularly monitor performance and profile your game to identify and address new areas for memory savings.

  2. Balance Optimization with Visual Quality. Don't sacrifice visual fidelity for the sake of optimization. Strive for a sweet spot that delivers a smooth gameplay experience without compromising aesthetics.

Tips :-

  1. Do NOT use the Resources directory.

  2. Use Addressables.

  3. Disable auto-sync transforms.

  4. Enable texture streaming.

  5. Use animation compression.

  6. Use mesh compression.

  7. Use the deep profiler to analyze your loading times.

  8. Use your target’s native texture compression format to avoid run-time texture transcoding.

  9. Disable read/write texture import flag where possible.

  10. Avoid expensive code in Awake, Start, OnEnable and OnDisable methods.

  11. Try Load in background and streaming for long AudioClips.

  12. Atlas your textures and reduce their sizes.Tools like Mesh Baker will help you.

  13. Remove direct references to assets that are not needed immediately or convert them to indirect references.

  14. Be selective about AudioClip import settings: do not overuse Decompress on Load.

  15. Avoid direct references to stop loading assets upfront. Use indirect references instead through the Addressables for more control.

Conclusion :-

By implementing these memory optimization techniques, you can significantly reduce memory consumption and boost the performance of your Unity games. This, in turn, leads to a more satisfying experience for your players and a greater chance of success for your project. So, squeeze more out of less, optimize your memory usage, and unleash the full potential of your Unity games!

"Congratulations! You've officially become a memory optimization warrior! Now go forth and conquer those memory leaks, banish the dreaded crashes, and create Unity games that run beautifully on any device. Remember, a well-optimized game is a happy game (and a happy developer)!"

I hope you found this blog post enjoyable!


More Blogs

See All