Search Unity

Basic Garbage Collection Game Concepts

Discussion in 'General Discussion' started by JForrestna, Sep 16, 2020.

  1. JForrestna

    JForrestna

    Joined:
    Jun 22, 2017
    Posts:
    10
    Ok, so I have a game where each level is stored on its own individual gameobject with a common parent containing all the levels/maps and the contents (prefab names as well as other relevant serialized information like script names and public values, etc) of the gameobject can be dynamically loaded, unloaded and edited (via my custom script). ALL objects for each level are are loaded as a level is loaded and if anything else existed, unloaded (So save if changes are made!). When playing the game, I would like to use gc.collect(2, forced) after the level is loaded and before the level is shown or after its unloaded.

    Question(s):
    Is there a better way to dynamically store the "maps/levels" that can also be loaded, edited and saved other then what I'm doing? Would files (also strings correct?) be better instead of on a gameobject? Does this open up the game to external changes made by others? My method ends up with TONS of strings and I know forced garbage collection is frowned upon.

    Next:
    Those prefabs that are loaded will only be enabled if within viewing range (throughout the game) and anything outside of the range disabled.

    Question(s):
    We are talking 500-2000 or more gameobjects that have been pre-loaded when the map/level has been loaded. Should the gameobjects be destroyed once behind you as you are playing? Or should I wait until the level has been completed before unloading all gameobjects? Will the GC cause issues and spikes if they are destroyed when behind you and no longer used?
     
  2. adehm

    adehm

    Joined:
    May 3, 2017
    Posts:
    369
    Only activate/deactivate objects until some sort of break in the game in where you can then create garbage and GC.Collect() before restarting gameplay.
     
  3. sonofbryce

    sonofbryce

    Joined:
    May 19, 2009
    Posts:
    111
    Do like adehm says, imo.

    As for the rest about level loading, I don't follow completely but my attitude with such things is just to try it and see what your memory usage is looking like for loading an entire stage. Find what your max memory usage would be for loading an entire level at the max size you imagine and see how it performs on your device.

    It depends on what your needs may be, but I feel like many things aren't necessarily worth taking major steps towards optimizing unless you need to optimize them. There may be other more important things to pay attention to for your optimization focus. It doesn't sound like you've been encountering any problems so far.

    So if you're working on a mobile, be sure to test FPS and memory usage often on device and see how the GC affects it.
     
  4. JForrestna

    JForrestna

    Joined:
    Jun 22, 2017
    Posts:
    10
    What I was essentialy saying is all 100+ levels are serialized on one specific gameobject in a list of "levels". When a level is being loaded, ALL previously created gameobjects are destroyed and the new level gameobjects are dynamically created. Downsides are accidental deletion of gameobject (ALL levels lost), Memory of all stored information.

    I would like to note that I changed that and its no longer true.

    All levels are now stored in a serialized individual file for each level. So now, when a level is loaded, it will load the file and create ALL gameobjects and scripts, filling those scripts values with whatever is in that file. I did this so I could easily ADD levels through downloads, etc (Players premade levels, whatever). I find a level takes about 1 second to load on an Samsung 5 with this method and uses less memory then having ALL the information stored on a gameobject. Memory still runs at 1gb though (in Unity's editor) and I think that's high for a game but I remember reading somewhere that Unity's editor doubles the actual memory (Objects are created twice?).
     
  5. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    You need to test on your target platform with builds. Yes, the Editor itself uses a whole bunch of memory, because it's a whole other piece of software essentially wrapped around your game. In addition to that, some memory behaviour is different in the Editor than in a build in order to help the Editor do its thing. So some stuff that allocates in the Editor (eg: doing a GetComponent() that doesn't find a matching component) won't allocate in a build.
    I would say so, yes. This gives you much better control because stuff doesn't have to all be loaded up front.

    If the whole level fits in memory then there's nothing to be gained by destroying stuff that's "behind" the player. In fact it could make things worse, because you're spending CPU resources to free up memory that you don't need.

    Load a level in. Keep using it until you're done with it. Destroy the whole lot at once when you're finished.

    In the context of Unity there is very little benefit to cleaning up old, unused resources until you might need to use them again.

    My suggestion is to read up on how C# memory management works, and also on how C++ memory management works. In Unity we're actually playing with an unusual hybrid of both.

    Short, useless answer: No, the GC itself will not cause spikes if you destroy stuff. But that's ignoring a whole bunch of stuff that matters at least as much.

    Longer, useful answer: The GC won't cause spikes, but other stuff might.

    The GC won't cause spikes because the GC doesn't actually care when you destroy stuff. That sounds counter intuitive, but see my suggestion above: there's little benefit to cleaning up resources until you know you need them again, and C# is designed with that in mind. When the runtime needs to allocate more memory for something, under certain conditions it'll ask the Garbage Collector to do a cleanup first, because that's when it matters. Think of it as not asking for more resources until it's using up what it already has.

    Another reason for this is that C# doesn't usually know when we "destroy" something. The vast majority of the time we just change the values of references and leave the rest up to the runtime. When I change the value of a reference the C# runtime doesn't know whether there are other references to the object it pointed at, so it can't assume I've "destroyed" it.

    The reason that we can explicitly "destroy" many things in Unity in C# is that these are commands passed over to the C++ side of the engine, where things can be explicitly destroyed. Unity has special behaviour on the C# side of things to account for this. Getting to the practical bit... this means that there potentially is a performance impact for destroying GameObjects and Components. It's probably pretty small, but Unity does do a bit of work when you destroy stuff. Any OnDestroy() methods in attached scripts are called, physics data structures need to be updated if there were any colliders, and so on.

    So the GC isn't going to cause spikes, but you're still making the engine do some work at a time when there's no benefit. You don't need the RAM you're freeing up, and you're doing work during a time where performance matters that could be delayed until it doesn't.

    If this stuff is of interest to you I suggest reading Jason Gregory's Game Engine Architecture book. It's not specific to Unity, but will open your eyes to the kind of stuff that goes on under the hood in a game engine.
     
  6. adehm

    adehm

    Joined:
    May 3, 2017
    Posts:
    369
    To expand on that for the op; although the garbage collector is not going to decide to clean up memory when you destroy something it will choose to sooner or later, which could be the next frame. If trying to rely on the garbage collector to clean up the memory you will have lag spikes as a consequence. It is best to create none unless you are trying to use the incremental garbage collector in which case you must activate it and still try to create as little as possible.
     
  7. JForrestna

    JForrestna

    Joined:
    Jun 22, 2017
    Posts:
    10
    Thank you for the long version. I have since my original post spent a day re-writing my scripts to save, load for edit and load for playing each level (I will explain in 1 sec) in individual files (json format). Reason, each level could instantiate thousands of objects. when I Load a level to edit in unitys editor, I wish to show EVERY object on that level. saving and playing are different though. Instead of saving EACH object individually, I decided to reuse each object when I can when you are playing, thereby cutting the number of actual objects loaded by 1/10 or more depending on the size of the level (my script automatically decides all this. As an example, my 1st level uses 985 objects, but only 117 are loaded and reused). I started to remove and destroy the objects as they are no longer needed to speed up iteration through the list of gameobjects every few seconds, but If you recommend against that, I will comment out that part of the code.
     
    Last edited: Sep 29, 2020
  8. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    I only have 2 recommendations.

    1. Focus on getting the game finished. Going back and re-doing stuff to make it theoretically better is time taken away from things which potentially matter much more.

    2. Stop worrying about imaginary performance, and start using the Profiler. Changing your game's inner workings based on how things might perform is a bad idea. If you're trying to optimise something then the first step should always be to measure current performance and resource usage. You need to clearly identify an actual bottleneck, not an imaginary one. When you know where the bottleneck is you can make changes informed by that. And then you need to measure again to see what impact the changes had to see if it helped, hindered, or didn't do much.

    Is iterating through that list actually slow enough to be noticed? Even if it is there are other ways you could speed that up. But that's jumping the gun.

    This is called "pooling" and it can have a huge, positive impact on performance in some circumstances. In others it can make things worse. So, have you checked the Profiler while running on a target device to see your actual performance with and without it?

    Note that you now have fewer objects, but you're now moving them all around. Do you know which of those approaches has a bigger impact on performance?

    Note that having 90% fewer GameObjects does not mean that you're using 90% fewer resources. If 100 GameObjects all use the same mesh, material, textures, etc. then all of those resources are just loaded once and shared. The only part that's duplicated is essentially the list of properties you see in the Inspector.

    So, I repeat: if you really care about performance then you need to start measuring things. Otherwise you're guessing and might make it worse.
     
    NotaNaN likes this.
  9. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,659
    I agree with the second sentence 100%, but not entirely with the first...

    You should definitely start using the profiler, but in addition, you need to set some performance budgets. If you want to run at 60FPS, you have 16.6 milliseconds to spend, every frame; how do you want to spend it? How much should go on rendering, how much on physics, how much on AI, how much on gameplay scripts, etc? You may need to do some experiments with different workloads on different devices to get a sense of what numbers are actually reasonable.

    Once you have decided your budgets, then you use the profiler to see whether you are under budget or over budget. If you are over budget, then you stop and optimise stuff... but if you are under budget, then do what @angrypenguin says, stop buying performance that you don't need, and focus on getting the game finished.
     
    angrypenguin likes this.
  10. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    In the long run, for sure. Aside from overall frame times I think that might be getting a bit ahead of where the OP is at the moment, though. Until they know how things generally work they have no basis to come up with "reasonable" numbers within frames.
     
    superpig likes this.