Objective

Main objective of this blog post is to explain how to Manage Memory in Unity Project.

 

 

Step 1 Introduction

When we create an array, string or object then the memory will allocate to a central pool, which is called the HEAP. When those particular things are not used for a long time then memory will be used for something else. In history, it was totally up to the programmer to allocate and release the memory blocks of heap explicitly with the related function calls. Now, memory management is done automatically by runtime systems like Unity’s Mon develop engine. Auto memory management requires minimum coding effort than explicit allocation/release and reduces the potential for memory leakage.

 

Step 2 Reference and Value Types

When any function is being called, then the values of its arguments will be copied to an area of memory reserved for that particular call. Data types, which allocate only a few bytes, can be copied very quickly and easily. However, it is common for arrays, objects and strings to be much bigger and it would not be very efficient if these types of data were copied on a regular basis. Fortunately, this is not compulsory; the actual storage memory for a big item is allocated from a small ‘pointer’ and the heap value is used to remember its memory address. After that only the pointer needs to be copied during the argument passing. The item, which is known by the pointer, can be located by the runtime system, a single copy of a data item can used as per requirement.

Types that are copied and stored directly during argument passing are called value types. These include char, floats, integers, Booleans and Unity’s struct types (for example, Color and Vector3). Types that are stored in heap area and then accessed via pointer are called reference types, since the value stored in the variable merely refers to the actual data. Strings, objects and arrays are the examples of reference types.

 

Step 3 Memory Allocation and Garbage Collection

Unused memory area of the heap is continuously tracked by the memory manager. The memory manager will allocate the unused space when a new block of memory is requested. This procedure will happen until the subsequent requests have been handled. It is unlikely that each of the memory allotted from the heap remains in use. A reference item on the heap can only be accessed as long as there are still reference variables that can locate it. If all the references of a memory block are released then the memory it occupies can safely be reallocated.

To identify which heap blocks are not in use, the memory manager searches through all the active reference variables and marks them as ‘live’. At the end of the searching process, the memory manager considers any space between the live memory blocks as empty and also can be used for subsequent memory allocations. The process of positioning and releasing the unused memory is known as garbage collection (GC).

 

Step 4 Memory Optimization

Garbage collection is not manual and cannot be visible by the programmer but the collection process actually requires significant Central Processing Unit (CPU) time behind the scenes. When used accurately, automatic memory management will generally be equal to or beat manual memory allocation for overall performance. However, it is necessary for the developer to avoid mistakes that will trigger the collector more often than necessary and introduce pauses in the execution process.

There are some algorithms that can be Garbage Collection nightmares even though they seem innocent at first sight.

 

Step 5 Example

functionStringConatinationExample (integerArray: int[])
{
varmyLine=integerArray[0].ToString();
 
for(var i=1; i<integerArray.Length;i++)
{
myLine +=”,”+integerArray[i].ToString();
}
return myLine;
}

The main point to learn here is that the new parts don’t get added to the string in place, one by one. What actually happens is that each time around the loop, the last contents of the myLine variable becomes dead – a whole new string is assign to contain the original piece plus the new potion at the end. Since the string gets larger with increasing values of variable I, the value of heap space being consumed also increases and so it is very easy to use up thousands of bytes of free heap space each time this method is called. To concatenate, use System.Text.StringBuilder class.

However, even repeated concatenation would not cause more troubles unless it is called frequently, and in Unity that usually implies the frame update.

For Example:

varmyScoreBoard: GUIText;
varmyScore: int;
function Update()
{
varmyScoreText: String=”My Score: “+myScore.ToString();
myScoreBoard.text=myScoreTxt;
}

It will allocate new string every time when the Update function is called and will generate a constant trickle of new garbage. Most of this can be saved by updating the text only when the myScore changes:-

 

5.1 Updated Example 1

varmyScoreBoard: GUIText;
varmyScoreText: String;
varmyScore: int;
varmyOldScore: int;
 
function Update()
{
if(myScore != myOldScore)
{
myScoreText = “My Score: “+myScore.ToString();
myScoreBoard.text=myScoreText;
myOldScore=myScore;
}
}

One more problem occurs when an array value is return by function:-

functionRandomList(numberOfElements:int)
{
varmyResult=new float[numberOfElements];
for(var i=0; i<numberOfElements; i++)
{
myResult[i]=Random.value;
}
returnmyResult;
}

This kind of function is very elegant and convenient when the new array is filled with values. However, if it is called constantly then new memory will be assigned each time. Since the arrays can be very big, the free heap space would be used up speedily, resulting in repeated garbage collections. To avoid this problem, make use of the fact that the array is a reference type. An array passed into a function as an argument can be changed within that function and the results will remain after the function returns.

 

5.2 Updated Example 2

functionRandomList(myArray: int)
{
for(var i=0;i<myArray.Length;i++)
{
myArray[i]=Random.value;
}
}

This simply replaces the existing values of the array with the latest values. Although this requires the initial allocation of the array to be complete in function calling code (which looks somewhat elegant).

Garbage Collection

As mentioned, it is great to avoid allocations as far as possible. However, given that they cannot be fully eliminated, there are two main strategies you can use to reduce their instruction into gameplay: -

 

5.3 Quick and frequent garbage collection (with Small Heap)

This strategy is best for those games that have large gameplay where a smooth frame rate is main thing. A game, which typically allocate small size block frequently, but these blocks will be in use only briefly. The size of heap when using this strategy on iOS is about 200KB and garbage collection will take about 5ms on an iPhone 3G. If the heap size increase to 1MB, the collection will take about 7ms. It will be beneficial to request a garbage collection at a regular frame interval. This will generally make collection happen more often than strictly necessary but they will be processed quickly and with minimal effect on gameplay:-

if(Time.frameCount % 30 ==0)
{
                System.GC.Collect();
}

However, you should use this technique and check the profiler statistics to make sure that it is really reducing collection time.

Slow but infrequent garbage collection (with Large Heap)

This strategy is the best for those games where allocations are relatively infrequent and can be handled during pauses in gameplay. It is helpful for the heap to be as big as possible without being too big as to get your game killed by the OS due to low system memory. However, the Mono runtime avoids expanding the heap automatically if at all possible. You can expand the heap manually by preallocating some placeholder space during startup (ie, you instantiate a “useless” object) that is allocated purely for its effect on the memory manager):-

function Start()
    {
        var tmp =new System.Object[1024];
        //make allocations in smaller blocks to avoid them to be treated in a special way, which is designed for large blocks
        for(var i : int=0; i<1024; i++)
            tmp[i]=new byte[1024]
    }
        //release reference
        tmp=null;
    }

A sufficiently large heap should not get completely filled up between those pauses in gameplay that would accommodate a collection. When such a pause occurs, you can request a collection explicitly: -

System.GC.Collect();

Again, you should take care when using this strategy and pay attention to the profiler statistics rather than just assuming it is having the desired effect.

 

Step 6 Reuse of Object Pools

There are number of cases wherein we can avoid generating garbage simply by reducing the number of objects that get created and destroyed. There are some types of objects in games, such as projectiles, which may be encountered over and over again even though only a small number will be in play at once. In cases like this, it is mostly possible to reuse objects rather than destroying old ones and replacing them with the new ones.

I hope you find this blog is very helpful while you working on Memory Management in Unity Project. Let me know if you have any question regarding Memory issue in Unity Project please comment here. I will reply you ASAP.

Got an Idea of Game Development ? What are you still waiting for? Contact us now and see the Idea live soon. Our company has been named as one of the best Unity 3D Game Development Company in India

I am 3D Game Developer and Android Developer with an aspiration of learning new technology and creating a bright future in Information Technology.