foreach loop iterator over child components of a certain type. Horay!1
Some pre-requisites: I assume you know Unity, scripting and C# well enough to follow through a dozen lines or so of example code. I won't explain what a for-loop does or what an interface is. I assume you know what a garbage collector does and some basics about IL and the .NET memory model like stack and heap.. Also, I won't talk about how you actually profile your memory allocations (which is probably the first thing you should do before start changing code randomly).
If you want me to write about any of these topics - comment section is below ;)
The Problem
So what is the issue with the Unity's garbage collector? See, the Garbage Collector that Unity 5 uses is synchronous. It has to halt the whole mono run-time to operate. And collecting garbage takes longer, the more trash (unreferenced memory) there is. In really bad circumstances, a GC sweep can take several dozens of milliseconds. That means game stutters. To avoid that, you simply "do not trash memory". Doh! If you can live without some unnecessary allocations, just remove them and makes things better. Or, you just don't deallocate memory but re-use it. But where are these allocations come from and how can you avoid the most common problems? There are some interesting places in C# and the .NET run-time, where allocations/deallocations just happen. Knowing these cases and what alternatives you have - that's what this blog series is about. In further postings, I will delve into the more exotic ways you could generate memory trash. But for starters, lets stay with the probably most common, easiest to avoid and biggest source of unnecessary memory garbage2: Straight-forwardnew allocations.
Avoid allocating function calls
Imagine a function that.. lets say.. darken the color of all renderer of the clicked game object and all its children by 50%. (Why would you want to have this? I have no idea..)
void OnMouseDown()
{
Renderer[] allRenderer = GetComponentsInChildren<Renderer>();
for (int i = 0; i < allRenderer.Length; i++)
allRenderer[i].material.color *= 0.5f;
}
Where is the allocation? Well, in line 3 you call the Unity function GetComponentsInChildren<T>() which returns a list of components in a newly created array. This function allocates the memory every time you call it. So the easiest thing is: Just don't call it every time. Cache the result in Awake and be done with it.
Renderer[] allRenderer;
void Awake()
{
allRenderer = GetComponentsInChildren<Renderer>();
}
void OnMouseDown()
{
for (int i = 0; i < allRenderer.Length; i++)
allRenderer[i].material.color *= 0.5f;
}
Now that was easy. You know this already, right? I am sorry for even mentioning this. Naturally, pre-caching in Awake (or even as a pre-build step) is what you would do in almost all real-life situations when you have data that doesn't change during run-time.. So for the sake of this example, lets assume that the number of collider could change at any time so we can't just pre-cache the list.
To still prevent the allocation every time you collect the renderer, many of Unity's Scripting API functions have another version where you provide the resulting list by yourself - typically as a List<T>.
List<Renderer> allRenderer = new List<Renderer>();
void OnMouseDown()
{
GetComponentsInChildren(allRenderer);
for (int i = 0; i < allRenderer.Length; i++)
allRenderer[i].material.color *= 0.5f;
}
This simple change will get you a long way in terms of memory allocations. Almost all functions that "collect some stuff" have variants that take a list as input instead. Like certain GetComponent* overloads, Physics.Raycast or even using Mesh.GetUVs instead of the Mesh.uv property.
Next time, I will start about "hidden" allocations like boxing of value types and some strange places they can occur.
Ciao, Imi.
1 You might be totally unimpressed because: a) You already know how to do this. Then you are awesome! Or b) You totally not care about the 24 bytes of memory garbage any
foreach call generates in Unity's mono version. Or you hate foreach anyway and wished Unity would use plain old K&R C. That's fine. See you some day! Byebye. Or c) You do care about perfect game memory performance but didn't know that foreach is a problem. Then this blog-series might be something for you..↩
2 Actually, the biggest and most common might be "Unnecessary instantiation of GameObjects". Like.. if you create a new bullet GameObject instance every time you fire a gun. But there are a gazillion posts about pooling GameObjects already out there, so.. maybe later. First, lets talk about something else.↩
No comments:
Post a Comment