Another Garbage Collector Issue

I thought I had my head around garbage collections but this one is driving me mad! The initialisation methed of one of my class can generate 1MB of garbage each time it is called…

The reason why makes no sense to me so I have made an extremely simple example:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class Spawner : MonoBehaviour
{
    public List<Confused> data = new List<Confused>();

    void Start()
    {
        SecondTest();
    }

    void SecondTest()
    {
        for(int i = 0; i < 100; i++)
        {
            data.Add(new Confused());
            data[data.Count - 1].Initialise();
        }
    }
}

This class spawns an example class 100 times on start. I don’t see anything here that should generate garbage since the variables are ‘global’. The Confused class:

using UnityEngine;
using System.Collections;

[System.Serializable]
public class Confused
{

    public enum Test { First = 0, second, third }

    public Test[] data;
    public Vector3[] data2;
    public float[] data3;

    public void Initialise()
    {
        //data = new Test[65536];
        //data2 = new Vector3[65536];
        data3 = new float[65536];
    }
}

I was testing to see if the enum is what causes my garbage but nope. Even an array of floats generated like this will produce 256kb garbage each time this is run!!

Is it just me or is this wrong? None of the references have been broken so I don’t understand why this is being marked as garbage, you can even see the data in the editor (thus the serialize tag).

Unity has got a new and more modern GC listed as a future task which is what is making me question this but I don’t want to file a bug report in case I have missed something trivial.

As a note if I create an empty list and add values to it 65536 times then 0 garbage is generated as expected. I am wondering if this is to do with using such a high number which I need in my project (it lists blocks in a chunk for a minecraft game, shock horror :P)

if your going to store huge collections of data, then I would reccomend a couple pointers:

  • Use Structs, not Classes. while structs have more limitations, classes have a larger allocation footprint and just take up more memory overall.
  • Make your data Immutable. as in its state can’t change. you’ll have to create a new object each time its state changes, but locking an instance’s state simplifys both equality comparisons and multithreading. also having a default immutable object (like Vector3.zero) allows you to quickly copy that instance as a new instance. so make your fields use either the const or readonly modifiers
  • don’t pre-allocate all that data at once. just allocate what you need for the next couple of frames. everytime you allocate data the GC tries to keep all that data contiguous in memory and pre-allocating large swaths of data (like over 85k, depending on the PC) can cause additional overhead. thats why Lists seem so efficient cause it doesn’t actually preallocate all 65535 addresses, just enough of what it needs right now

You should definitely not use structs for this; ideally a struct uses no more than 16 bytes. You would not put an array in a struct, since they are supposed to represent a single value, but even if you did, in this case there would be no real difference in memory usage.

Anyway you’re reading this wrong; it’s “GC allocation”. It’s not marked as garbage, it’s simply allocating memory, which is of course 100% necessary. It will be garbage collected when there is no longer any reference to the data, and this shows up as “GC.Collect” in the profiler.

That’s not what happens though; creating a List and adding 65K values certainly does allocate memory. Even if you created a List of a class and simply added null 65K times, it allocates memory since it needs 8 bytes (as I recall) for each of those references, so 512K total at least.

–Eric

1 Like

Hey folks thanks for the replies.

Ah good catch there Eric, thank fuck! A bit misleading though since I have garbage problems as well (I was seeing spikes with my GC taking 67% of the frame time) and these values stick out like a sore thumb!

Hard to ignore that 0.77GB! Managed to get the GC down a ton in the image above but still clearly noticable.

is it just me then or is tracking down garbage pretty impossible to do in the profiler? I have no way to distinguish from this if that allocation is safe or not without trawling through code :frowning:

I am tempted to go for the option of manually calling GC.Collect at sensible intervals but feels a bit sloppy without knowing exactly when its generated.

P.S I realise garbage is the least of my problems in the image above :stuck_out_tongue: It only constitutes the blip at the end of those blocks between 30 and 60FPS

Wait no now really confused :confused: Lists are generating the allocation as well, which makes sense with what Eric is saying. Not sure what I saw to make me think they worked fine. However in that example above a GC is still being triggered:

This is with an empty default scene and the example code added above but adding to a list the 65K times. Why would that code trigger a GC?

If you initialize a list with zero initial capacity new List<T>() and call List.Add(T), the internal array will be reallocated every time causing a lot of garbage. This may be causing at least some of your garbage problems. In the script you posted, try using public List<Confused> data = new List<Confused>(100);

1 Like

Not every time, but yes, once the capacity is reached (default capacity being 10 I believe) it will re-allocate the internal array to increase space. I believe it doubles each time, so 20, 40, 80, and so on.

Ahh duh good point thanks! I should have slept before making that code. My garbage in that example is gone, including the allocations in the profiler.

However still present in my project where I had already initialised the size of the lists, dictionaries etc as needed so still really confused, probably time for a new example/angle.

Thanks for the input folks!

The default capacity before doubling is 4, but otherwise yes. It’s worth specifying an allocation if you know the list will have at least that many elements, however since the capacity is doubled when necessary rather than increased every time you add something, it’s usually not worth worrying about too much. (Also the capacity is not reduced when removing elements, unless you use TrimExcess, but that’s rarely a good idea.)

–Eric

2 Likes

I feel like a lot of new Unity users have this fundamental misunderstanding about what Garbage Collection means because they read a few vague posts about how it’s “bad”. There’s no such thing as “garbage” in the way you are using it. A Unity program doesn’t randomly create piles of trash for no reason that you have to clean up. Garbage Collection is a system where, to put it simply, after you call new(), instead of manually calling delete() later like in C++, the GC deletes it for you later when it detects you’re no longer using it. That’s all it is. Anytime you call new(), it’s going to allocate memory (that’s what new() means) and it will show up in the profiler on that GC line. There’s no such thing as a “safe” allocation compared to a “garbage” allocation; there’s maybe just allocations you didn’t realize you were doing.

This. Doubling a List throws the old list straight to the garbage collector.

If you know the approximate size of the list, then create it with a size, give yourself plenty of room, in most cases the size of a list won’t hammer your total memory that much. CPU time is often more precious then memory.

Of course if you know the exact size of the list in advance, use an array instead.

Wow someone woke up on the arrogant side of the bed this morning!

Just trying to help. If you post a code snippet where you’re calling new() and don’t know why it’s allocating memory, it seems like maybe you’re not aware how it works.

Most of the time GC issues can be hunted down by looking for new.

However in this case the new is hidden deep inside of the List implementation. It’s not immediately obvious why adding 100 items to a List in a loop generates garbage.

1 Like

He’s adding 100 items with new() to the list, and then using new[ ] to put arrays in the list… that’s where the vast bulk of the memory allocation is. List<> has its own memory as well, but that wasn’t really the original question. In fact it was Eric who first said “That’s just how memory allocation works” and Laireon said “Oh” but whatever, I apologize if I offended anyone. :stuck_out_tongue:

?? Seemed perfectly informative to me. With luck it will show up in relevant Google searches…I tend to view forum topics as not just replying to a specific person, but as a resource for future readers.

–Eric

1 Like

Not me. I just realised it was the OP complaining about your post. OPs generally don’t get the right to complain about anyone trying to help.

Programmers are arrogant. Deal with it. :stuck_out_tongue:

1 Like

I guess I just didn’t like the tone which is of course hard to properly convey over the internet. I had made a trivial mistake in my example, as I knew it would be since I knew I was tired and not thinking straight. So to me I read that post as ‘Eugh begginners…’ so I responded in kind :stuck_out_tongue: (as I was taking it at least).

To me even the term there for GC Alloc is still pretty vague. I get it now but in my head what it mean was that in that frame, X had been allocated to the GC to be cleaned on the next collect which I think is a perfectly rational assumption from that name alone. The real meaning of course also makes perfect sense (its simply allocated, doesn’t mean its actually garbage, its just being tracked).

So anyway since his reply was to my comment of “is it just me then or is tracking down garbage pretty impossible to do in the profiler?” it just irked me a little since I still stand by this. This line was me realising my assumption of what the profiler tracks is wrong and was hoping people had suggestions of better ways of using it.

Edit:
Anyway Makeshift has apologised and I will do the same for being a bit jumpy. Lets move on :stuck_out_tongue:

1 Like

No! No moving on! The rules state you two must now duel to the death, no exceptions. Your weapons are…::rolls dice::…erm, Nerf guns apparently. Might be a long duel…

–Eric

5 Likes

I’m ready.

2848680--208064--1747078b6c2940b5b7624422f3dafc8c.jpeg

4 Likes