Welcome, Guest. Please login or register.

Login with username, password and session length

 
Advanced search

1411276 Posts in 69323 Topics- by 58380 Members - Latest Member: bob1029

March 28, 2024, 12:05:51 PM

Need hosting? Check out Digital Ocean
(more details in this thread)
TIGSource ForumsDeveloperTechnical (Moderator: ThemsAllTook)Reducing memory allocations to avoid Garbage Collection on Unity
Pages: [1] 2
Print
Author Topic: Reducing memory allocations to avoid Garbage Collection on Unity  (Read 9366 times)
Grhyll
Level 2
**



View Profile WWW
« on: January 20, 2016, 10:08:54 AM »

Hi! I'd like to share here an article I wrote on my blog, which was also featured on Gamasutra.


In this technical post, I’m gonna talk about optimization, and specifically about memory allocations in Unity.

Disclaimer: I’m in no way an expert in this area, and you should take anything from this article with caution, as it may not be perfectly accurate, or there may be better solutions than the ones I give. Let’s say it’s a basic overview of tips about reducing allocations.

Second disclaimer: Some methods are here to reduce allocations, but it sometimes goes against speed performances (reasonably). Just choose what you’re looking for; it is for example more important on a mobile app than on a desktop game.


Short introduction

Working on my mobile game, I didn’t care too much about performances or allocations (I usually try to write clear, flexible, readable code without doing premature optimization) until I faced issues on device. I began with some obvious fixes, like packing sprites to improve compression, which made the game a lot more playable. Still, there were frequent micro-freezes that were almost not noticeable at first, but became unbearable as soon as I took note of them.

After some digging and playing around in the profiler, I found out those were due to some line named GC.Collect. I knew what garbage collection is, but didn’t really think about it until now, since it had never been an issue since I learned about it in school. In my project, it can take up to 8.62ms (on my high-end pc) of the 9.79ms needed for my behaviours updates on a given frame; I didn’t check on device, but it was enough to be seen.


Scary, isn’t it?

From Unity doc: “When an object, string or array is created, the memory required to store it is allocated from a central pool called the heap.” And the GC.Collect, which can happen anytime, as soon as the heap requires it, empties this memory by collecting garbage, looking through all the objects to see if they are in use or not. This operation can take quite a long time if there are many objects, dead or still alive.

Since I use pooling for almost everything in my game, I couldn’t really imagine where those alloc came from (the place it shows up in the profiler isn’t necessarily related to what really caused it). So I began investigating about this on the interwebz, and here are some tips on mistakes to avoid and methods to track them.


Reuse

The first obvious thing to say is: don’t create “new” things once your game is initialized/your scene is loaded. For your prefabs and even for components, use pooling and immediately instantiate everything you may need later. It also applies to arrays, list and dictionary, for example in an Update function where you have to fill an array every frame. Create one when the class starts, giving it the expected size as parameter, then clear it when you need it empty, instead of recreating it. It will keep its allocated space, and you can fill it again without extra allocation. Those are often very small allocs, but if they keep happening every frame, they will eventually cost more than the bigger ones that happen less frequently.

If you have a method filling an array or a list, pass it as an argument of the function instead of making it its return value. Have your collection be a member of the class, initialized only once, then simply cleared and filled inside your method, so you don’t have to instantiate a new one every time.

The strings create garbage as well, even when you concatenate them, so if you have to handle a lot of text somewhere and manipulate it heavily, use a StringBuilder that will take care of those memory issues alone and avoid the creation of garbage.

In the same spirit, cache everything you need to check frequently. If GetComponent is supposed to only allocate memory in the editor and not on device, other functions like GetComponentsInChildren will never be good to use on a regular basis (which is obvious since it returns an array). So call it once if you need it, then keep the result to reuse it and update it if needed.


Best practices

Another well-known garbage creator is the foreach. Because of Boxing (I’ll let you look for it if you want to go in detail), a foreach loop used on a collection containing objects creates garbage, so just use a simple for loop.

Some basic methods or functionalities provided by Unity also generate garbage for weird reasons. The best way to track them is to use the profiler after encapsulating your code in Profiler.BeginSample(“SampleName”) / Profiler.EndSample(). Those sections will have their own line in the profiler, allowing you to find exactly what line generates garbage and finding those tricky allocs.


The result of a GetComponentsInChildren

Vector3.Equals, for example, generates garbage; just compare the coordinates of your vectors one by one. Also, don’t use enum values as dictionary keys, cast them to int. There must be several other tips of this kind which I did not have to deal with, so be sure to profile until you find the very line making the alloc.

Finally, you can trigger the garbage collector by yourself by calling System.GC.Collect(). I naively tried to call it every 30 frames, so that the garbage don’t have the time to grow, but the thing is that garbage collection duration will take as much time whether it has a lot to clear or not, because it scales with the total amount of objects. The best thing to do (besides not allocating anything) is to call this at the right time: just before starting the gameplay, when the player pauses the game, during loading of cutscenes… In those occasions, the collection spike will be invisible to the player (but your code still has to be clean enough so that it doesn’t need to be called outside of those circumstances).


Limits

Sadly, some of Unity’s methods generating garbage can’t really be avoided without doing some programming weird tricks. I couldn’t find anything about some obvious “leaks”, like Text.OnEnable or OnGUI functions, which can generate quite a lot of garbage if you use them frequently.


Changing game state, reenabling the HUD… That hurts.

In those cases, if you don’t want to recode yourself the whole system, you’ll have to be smart about the way you call them. Maybe you could just put that label offscreen when it’s not used, instead of disabling it? Rather than calling Clear/Stop on your particle system when it’s not used anymore, store its end date when starting it (don’t check each frame if it’s still alive!), and if it went until the end when you don’t need it anymore, don’t bother clearing it.

There are a bunch of other methods from Unity that allocate memory on the heap, some of which you can bypass, some of which you’ll just have to deal with. Just remember that it’s not always essential to reach 0 alloc; if a function called once in a while generates some KB of garbage, maybe it’s ok, as long as it’s not every frame. Optimize wisely.


Some other perf considerations

To end this note, I’d like to point out some very easy optimizations to improve performances:

On the rendering side, there is a lot to do. Pack your sprites in atlases, use an uniform scale on all of them in game, don’t modify the shared material of only one instance. In my project for example, I used an additive shader to make the enemies blink in white when hit, but every time I did that, a new instance of the material was spawned, and the enemies couldn’t be correctly batched anymore. Instead, I added a white sprite of the hit enemy, and just enable/disable it when needed (which fortunately doesn’t have the same memory impact as UI.Text).

On the physics side, it can often be easy to optimize things as well, if you started with a naive approach. Check your collisions matrix, remove all unnecessary cases. Try to use less colliders if you have several of them on some objects, prefer circle/sphere colliders to square/box or capsule ones. If you use Unity’s UI system, disable the “UI Raycast Target” option (checked by default) on everything that doesn’t need it.

Don’t leave empty Unity callbacks, especially like Update or FixedUpdate: because of the way they are called, it can be costly if you have a lot of behaviours, even if there is no code in them.

 

Well, that’s all. Please get in touch if you see mistakes or inaccurate advices so I can fix it before more people read it!

If you liked it and want more, don’t hesitate to subscribe to this blog, or follow me on Twitter: @Grhyll.

Thanks for reading!

Further readings:

Unity doc: understanding automatic memory management

Unity doc: mobile profiling

Unity blog post: 1k update calls

C Memory Management for Unity Developers (a bit old)
Logged

Programmer at The Game Bakers
3-50.net
Current project: oQo
RoboticPotato
Level 0
**


CEO


View Profile WWW
« Reply #1 on: January 20, 2016, 10:47:16 AM »

Great tips! Object pooling and that sort of stuff are definitely old C# tricks used in general software development as well.

Are there any slick tips with pointers/arrays/bitshifting you might be able to share for more advanced users? I've never gotten a hand on more complex stuff like that.
Logged

Visit the Robotic Potato!
http://roboticpotato.com
Grhyll
Level 2
**



View Profile WWW
« Reply #2 on: January 20, 2016, 02:10:59 PM »

Thanks!
Indeed some of those tips are not limited to Unity, more like global good practices Smiley

Regarding the topics you brought up, I can't think of anything more than what's in the article, related to garbage allocation. You can't do much about pointers in C# (at my knowledge level, at least); as said above, arrays are to be treated carefully in order to avoid unnecessary allocations, and I don't think bitshifting can really cause issues with allocations, can they?
I'll do some more technical articles in the next weeks (probably one every two weeks), but probably always related to Unity, like editor tools to draw custom widgets in the scene view.
Logged

Programmer at The Game Bakers
3-50.net
Current project: oQo
RoboticPotato
Level 0
**


CEO


View Profile WWW
« Reply #3 on: January 20, 2016, 03:39:37 PM »

Awesome, editor tools always get me because..I don't know, it's like the Handle/Inspector API totally blows.

I ask about bitshifting (or rather bitmanipulation) because that always gets tossed around coding interviews but I can't for the life of me figure out when I'd actually want to use it...

Seriously, when has anyone had to XOR something?
Logged

Visit the Robotic Potato!
http://roboticpotato.com
Grhyll
Level 2
**



View Profile WWW
« Reply #4 on: January 20, 2016, 11:19:22 PM »

Wel bitshifts are usefull when you have to handle masks for example, be it navmesh or physics layer. Things like
Code:
int layerIndex = LayerMask.NameToLayer("MyLayer");
int mask = (1 << layerIndex);
mask |= otherLayersMask;
if(mask & testMask != 0)
{
    blablabla...
}
However I neved had to use XOR operator, I think ^^'
Logged

Programmer at The Game Bakers
3-50.net
Current project: oQo
insaneinside
Level 0
**


think critically.


View Profile WWW
« Reply #5 on: January 21, 2016, 12:43:01 AM »

Seriously, when has anyone had to XOR something?

XOR is useful for toggling bits.  Assuming that C# uses the same character (^) as C and C++ for XOR operator:
Code:
int mask;
// ...
// turn on the fourth bit
mask |= 1 << 3;
// toggle the third bit
mask ^= 1 << 2;
// turn off the second bit
mask &= ~(1 << 1);

etc.  Bit-toggling is quite useful when programming microcontrollers, since bits often correspond directly to e.g. output pins.
Logged
drjeats
Level 0
*



View Profile
« Reply #6 on: January 22, 2016, 01:58:29 AM »

In Unity I've used it in its direct meaning to test for mutually exclusive conditions.

SerializedProperty.isArray is true when .propertyType is String, so I have this assert in some editor code to make sure I'm not processing a for-realsies-array:

Code:
Assert.IsFalse(serializedProperty.isArray ^ (serializedProperty.propertyType == SerializedPropertyType.String),
               "You should filter out arrays before this point");

Could have formulated it without XOR, but I like being concise  Shrug

Here's a similar situation from an input lib reconciling some global settings and passed args:

Code:
if (Invert ^ (IsYAxis && InputManager.InvertYAxis))
{
targetValue = -targetValue;
}


All the other uses I see in our codebase are from hash functions. Smiley
Logged
seu
Level 0
**


View Profile WWW
« Reply #7 on: January 28, 2016, 01:39:10 AM »

There is (at least) one case of pooling that is very specific to Unity, and it's about changing the parameters of a material in the MeshRenderer.

When you do something like:

Code:
var mr = gameObject.GetComponent<MeshRenderer>();
var material = mr.material;
material.SetFloat("SomeName", 1);
mr.material = material;

 Unity does a new copy of the material, no matter if you are using the same parameter. In my game Flip I do this for changing the appearance of pieces. At first I was generating multiple identical materials, until I implemented a (later generic and configurable) material pool.
 If anyone is interested in the code for the pool, let me know.
Logged

Check out Flip!<br />http://perroelectrico.com/flip
Grhyll
Level 2
**



View Profile WWW
« Reply #8 on: January 30, 2016, 03:37:16 AM »

Well I would be interested indeed, not sure I'd use it, but anyway it can be interesting to know how to do that!
Logged

Programmer at The Game Bakers
3-50.net
Current project: oQo
Sebioff
Level 1
*



View Profile
« Reply #9 on: January 30, 2016, 09:39:43 AM »

Alternatively you can use MaterialPropertyBlock for changing per-renderer properties without creating new materials.
Logged

Current devlog: Parkitect, a theme park simulation, currently on Kickstarter | Twitter
doihaveto
Level 2
**



View Profile
« Reply #10 on: January 30, 2016, 08:30:09 PM »

Good points about object pooling and similar reuse techniques, they can make a *huge* difference in avoiding spurious allocations!

One question, though: are you quite sure Vector3.Equals causes heap allocations? This is the decompiled source for it (via ILSpy):

Code:
// UnityEngine.Vector3
public override bool Equals(object other)
{
if (!(other is Vector3))
{
return false;
}
Vector3 vector = (Vector3)other;
return this.x.Equals(vector.x) && this.y.Equals(vector.y) && this.z.Equals(vector.z);
}

I'm not sure where allocations could be coming from, especially since Vector3 is a value type... Maybe in your use case you were accidentally boxing things by casting up to Object first, before calling Equals?
Logged

oahda
Level 10
*****



View Profile
« Reply #11 on: January 30, 2016, 11:59:08 PM »

Vector3 is a value type...
Thanks for finally confirming that to me. I'd note it working a bit differently. I thought any user-defined types allocated by new were always on the heap in C#, but I had indeed noticed Vector working weirdly compared to other classes in Unity... So how does one go about defining a value-type class in C#?
Logged

Grhyll
Level 2
**



View Profile WWW
« Reply #12 on: January 31, 2016, 01:45:26 AM »

One question, though: are you quite sure Vector3.Equals causes heap allocations?
Well I'm sure it did in the editor when I was working on this (without any cast), but maybe it doesn't in build, I couldn't say. I don't have very deep knowledges about low-level programming, and I know the word "boxing" because I encountered it quite a lot when working on those allocations, but it doesn't go any further for me, so my results mostly come from experimenting rather than theory.
Anyway, it is very easy to test it for youself with the profiler Smiley

Prinsessa => Not totally sure I'm right, but I would say that a struct is considered like a value-type, isn't it?
Logged

Programmer at The Game Bakers
3-50.net
Current project: oQo
seu
Level 0
**


View Profile WWW
« Reply #13 on: January 31, 2016, 03:41:18 AM »

Well I would be interested indeed, not sure I'd use it, but anyway it can be interesting to know how to do that!

The code itself is very simple, but it's also very generic and can probably be optimized for special cases.

Code:
  /// <summary>
    /// Generic material pool
    /// </summary>
    /// <typeparam name="T">A class or struct that contains the parameters for identifying and constructing a new material</typeparam>
    public class MaterialPool<T> {

        // The definition of a function that given a T will return a new material.
        // This is used when the material is inserted in the pool for the first time.
        public delegate Material MaterialGenerator(T t);

        private Dictionary<T, Material> pool;
        private MaterialGenerator generator;

        public MaterialPool(MaterialGenerator generator) {
            this.pool = new Dictionary<T, Material>();
            this.generator = generator;
        }

        public Material GetMaterial(T t) {
            Material mat;
            if (!pool.TryGetValue(t, out mat)) {
                mat = generator(t);
                pool[t] = mat;
            }
            return mat;
        }
    }

So you need additionally the generator method, and a class or struct that contains the values that make the material unique. In my current project (Demondrian) I use something like this:

Code:

        private struct MaterialDef {

            public readonly Type type;
            public readonly int size;
            public readonly bool ghost;
            public readonly bool fog;

            public MaterialDef(Piece piece, bool ghost = false, bool fog = false) : this (piece.type, piece.size, ghost, fog) { }

            public MaterialDef(Type type, int size, bool ghost = false, bool fog = false) {
                this.type = type;
                this.size = size;
                this.ghost = ghost;
                this.fog = fog;
            }

            public override bool Equals(object obj) {
                return (obj is MaterialDef) &&
                    ((MaterialDef)obj).type.Matches(type) &&
                    ((MaterialDef)obj).size == size &&
                    ((MaterialDef)obj).ghost == ghost &&
                    ((MaterialDef)obj).fog == fog;
            }

            public override int GetHashCode() {
                return (((ghost ? 13 : 11) * size) * 7 + type.GetHashCode()) * (fog ? 17 : 23);
            }
        }

So you can see that I override the GetHashCode and Equal methods to make sure that the pool will properly discriminate one type from the other.

And as a Generator method, I use:

Code:
        private Material MaterialGenerator(MaterialDef def) {
            Material mat = (def.ghost) ? ghostMaterial :
                GameObject.Instantiate<Material>(piecePrefab.GetComponent<MeshRenderer>().sharedMaterial);

            var color = GetColor(def.type);
            if (def.ghost) {
                color.a = ghostPieceAlpha;
            }
            mat.SetColor("_Color", color);
            mat.SetFloat("_Scale", def.size);
            if (def.fog) {
                mat.SetTexture("_MainTex", fogTexture);
            }
            return mat;
        }
(Of course, this part is the most dependent on what exactly you do and your generator function might be totally different)

As you can see all parameters from MaterialDef are needed for generating (and discriminating) different materials.
I could have used an existing class for this (i.e., some 'Piece' class) but it was not exactly appropriate in my case.

You later use this simply as:

Code:
            matPool = new MaterialPool<MaterialDef>(MaterialGenerator);
// and later, when you need a material:
            go.GetComponent<MeshRenderer>().material = matPool.GetMaterial(new MaterialDef(type, 1));

The good thing about this approach is that you separate correctly different concerns: the pooling, the identification of what makes one material different from the other, and the material generation itself. You can replace each one of them without worrying (much) about the other. Also, it allows you to just query for a material from the pool without caring if it will generate a new one or reuse one.

I just wrote all this very fast, so let me know if something is not clear. Smiley
Logged

Check out Flip!<br />http://perroelectrico.com/flip
oahda
Level 10
*****



View Profile
« Reply #14 on: January 31, 2016, 03:59:57 AM »

Prinsessa => Not totally sure I'm right, but I would say that a struct is considered like a value-type, isn't it?
Oh, I didn't know C# had a separate struct syntax. Is it exactly like a class, except with a different keyword in the definition and the fact that it yields value types instead of references? Or is it more restricted?
Logged

Grhyll
Level 2
**



View Profile WWW
« Reply #15 on: January 31, 2016, 11:33:11 PM »

I just wrote all this very fast, so let me know if something is not clear. Smiley
Thanks for sharing, it seems quite clear, although I was expecting something less specialized. I guess it could be generalized with something like hashtable for parameters.

Quote from: Prinsessa
Oh, I didn't know C# had a separate struct syntax. Is it exactly like a class, except with a different keyword in the definition and the fact that it yields value types instead of references? Or is it more restricted?
I'm not sure of myself enough to give you a solid answer, but I think it's like a "value type class" indeed. I don't use them a lot and haven't run deep experimentations so I may be wrong :/
Logged

Programmer at The Game Bakers
3-50.net
Current project: oQo
seu
Level 0
**


View Profile WWW
« Reply #16 on: February 01, 2016, 04:12:03 AM »

I just wrote all this very fast, so let me know if something is not clear. Smiley
Thanks for sharing, it seems quite clear, although I was expecting something less specialized. I guess it could be generalized with something like hashtable for parameters.

You mean for pooling or creating? For pooling, you cannot go more generic than this I think. You could use a hashtable, but it does not make a big difference as long as it's useful as a key for the dictionary. For creating, I think it does not make a lot of sense to try to make it super generic (as in, suitable for any project), it would also be quite hard and complex.
Logged

Check out Flip!<br />http://perroelectrico.com/flip
Grhyll
Level 2
**



View Profile WWW
« Reply #17 on: February 01, 2016, 05:30:34 AM »

Well I think that if the generation isn't 100% generic, then the pooling can't really be 100% generic either. I guess it would be possible to just give as parameter the keys of the shader attributes and the values needed, which would allow to handle any shader and any list of parameter, rather than those specific to the project, but there would probably be some more work to do to make it as efficient. (But I haven't tried your code, so maybe I didn't think through it enough!)
Logged

Programmer at The Game Bakers
3-50.net
Current project: oQo
Ravine
Level 0
**


View Profile
« Reply #18 on: February 01, 2016, 09:27:46 AM »

(Coucou Grhyll, le monde est tout petit hein?)

So, the whole Value Type vs Reference Type is one of the basic concept of C# that is often overlooked, and that has been an issue, especially for those coming from a C/Cpp background, where struct and classes are "the same"

An instance of a struct definition will be a Value Type. An instance of a class definition will be a Reference Type. This gives them a couple of properties and differences.

An interesting read on the differences of Struct vs Classes, Jon Skeet's TL;DR is worth a read http://stackoverflow.com/a/5057284 and for the more technical, don't hesitate to follow the link he posts (other answers can also be interesting to read).

You'll note at some point that you cannot inherit your structs, because well, Value Types cannot be inherited (this is related to the "plain data" concepts explained better by Skeet in the post above). You can have a quick read here for more info http://stackoverflow.com/questions/1222935/why-dont-structs-support-inheritance

But in a nutshell, let's assume that value type are basically "plain data" object (so you have to carry them around, all the time), while reference will be "data somewhere, here's where it lives", so you just keep a reference to it. (it's *similar* to pointers, but not exactly the same, so I won't talk about pointers).

Now, about the whole allocation thing around .Equals, the explanation has a name: "Boxing".

.Equals is a method of "object", the all-purpose-general-supertype. You probably already heard "Everything is object" in Java or C#. So you can test an instance of YourClass and see if it equals a Vector3, because they both are objects. But the problem is that "object" is a reference type ( https://msdn.microsoft.com/en-us/library/system.object%28v=vs.110%29.aspx ), because it's an instance of a Class.

So when we call .Equals on a Vector3 instance (which is a value type, remember?), we're effectively calling the object.Equals() version of it, because Unity's Vector3 does not override Equals(). And for that, Equals needs to be called on an object, so on a ... reference (everyone's still following?).

So what happens when you have myVector3.Equals(otherVector3), is boxing. The compiler is told to create a temporary object reference around myVector3, in a little box (effectively wrapping the values type in a reference containing the vector, let's call it [myVector3] )
It then calls [myVector3].Equals(otherVector3) and returns the result.
https://msdn.microsoft.com/library/system.reflection.emit.opcodes.box.aspx

But that reference is temporary data. It has been allocated. It's different from the actual myVector3, and will need to be collected some day. Hence the Garbage Collection spikes.

Funnily enough, Unity's Vector3 overrides the == operator, so you can work around that allocation issue by doing the myVector3 == otherVector3 equality test, which will enforce that you're comparing 2 Vec3 at compile time, and will not generate garbage. Win/Win.

Hope that explains it a bit (and that I havent been too confusing or wrong :D )
Logged
Ravine
Level 0
**


View Profile
« Reply #19 on: February 01, 2016, 09:45:50 AM »

While i'm at it, a couple of links from my collection

Unite 2013 - Internal Unity Tips and Tricks




C# Memory Management for Unity Developers (part 1 of 3)
http://www.gamasutra.com/blogs/WendelinReich/20131109/203841/C_Memory_Management_for_Unity_Developers_part_1_of_3.php
(part 2 and 3 are linked at the end of the post) This covers pretty much the same, with example and a bit more detailed info.
Logged
Pages: [1] 2
Print
Jump to:  

Theme orange-lt created by panic