Search Unity

Garbage Collection, Allocations, and Third Party Assets in the Asset Store

Discussion in 'General Discussion' started by Games-Foundry, Jun 21, 2012.

  1. half_voxel

    half_voxel

    Joined:
    Oct 20, 2007
    Posts:
    978
    The efficiency comes when say you want to query all components on 100 different objects. With the previous approach you would have to do something like.

    Code (csharp):
    1. for (int i=0;i<objs.Length;i++) {
    2.    MyComponent[] arr = objs[i].GetComponents<MyComponent>();
    3.    // do something with the array
    4. }
    Which would allocate 100 arrays.

    The new approach would look like
    Code (csharp):
    1. MyComponent[] arr = null;
    2. for (int i=0;i<objs.Length;i++) {
    3.    arr = objs[i].GetComponents<MyComponent>(arr);
    4.    // do something with the array
    5. }
    6. // the array goes out of scope here, no need to clear it
    7.  
    The above code would in the absolute worst case allocate 100 arrays in case every object had more components than the previous one. That case isn't really going to happen in normal code though. I would say it would at maximum allocate 4 arrays in normal cases.

    However I think GetComponents is a bad example case. The arrays it returns are usually very small, almost always less than 10 in size. A better example is the mesh.GetVertices method. It could easily return arrays with a size in the thousands and if you want to do that for 100 meshes, it will add up to quite a lot of garbage.
     
  2. Dustin-Horne

    Dustin-Horne

    Joined:
    Apr 4, 2013
    Posts:
    4,568
    Not at all. You can code and compile a separate .DLL in Visual Studio and drop it in. It is already compiled. You can use that same DLL for 3.5 through 4.2.2 (targeting .NET 3.5) without ever having to recompile it.
     
  3. half_voxel

    half_voxel

    Joined:
    Oct 20, 2007
    Posts:
    978
    Hm. That is true.
    So I guess it does break backwards dll compatibility.

    [EDIT] However for this specific feature it will work.
    Unity must only introduce a new overload instead of an optional parameter.

    GetComponents<T> () and GetComponents<T> (T[] buffer) for example.

    Then it will be backwards compatible with dlls.
     
    Last edited: Oct 30, 2013
  4. Games-Foundry

    Games-Foundry

    Joined:
    May 19, 2011
    Posts:
    632
  5. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,657
    I already proposed this back on page 8, except I proposed using List<T> instead of arrays. List<T> already has well-defined resize and length-tracking behaviours - there's no need for special 'null termination' or things like that.

    I don't think using default parameters in this way is a good idea. Better to just keep the existing overloads and provide the List<T>-based one as an alternative.
     
  6. half_voxel

    half_voxel

    Joined:
    Oct 20, 2007
    Posts:
    978
    Oh. You did. Hard to keep track of everything that has been said.
    Yes maybe List<T> is a better from a usability perspective, but it conflicts a lot more with the current implementation and is a bit slower.
    But I guess if UT is going to add some kind of change like this, they are probably going to go for usability.
     
  7. ronan-thibaudau

    ronan-thibaudau

    Joined:
    Jun 29, 2012
    Posts:
    1,722
    List<T> shouldn't be slower than arrays in any measurable way in any common scenario
     
  8. gregzo

    gregzo

    Joined:
    Dec 17, 2011
    Posts:
    795
    Hi to all,

    I've been following this thread for a while, certainly one of the most informative around.

    Trying to avoid GC spikes in real time sampling applications, I gave a shot at a little float[] pool system:

    -1 large float[] is allocated
    -It is dynamically fragmented as required
    -Chunks are requested and provided as objects which implement IList<float>

    I'm currently benchmarking ( using system.diagnostics.Stopwatch ), with extremely surprising results:

    Code (csharp):
    1. int _size;
    2.  
    3. void TestFloats()
    4. {
    5.     float[] test = new float[ _size ];
    6.     WriteFloats( test ); //nbOfTicks = roughly 30% of size
    7. }
    8.    
    9. void TestFloatsAsIList()
    10. {
    11.     float[] data = new float[ _size ];
    12.     WriteIListFloats( data ); //This is extremely slow, with nbOfTicks = size on my machine
    13. }
    14.    
    15. void TestVirtualFloats()
    16. {  
    17.     IList data = _floatsPool.GetChunk( _size );
    18.     WriteIListFloats( test ); //nbOfTicks = roughly 40 % of size
    19. }
    20.    
    21. void WriteFloats( float[] data )
    22. {
    23.     for( int i = 0; i < data.Length; i++ )
    24.     {
    25.         data[i] = Random.value;
    26.     }
    27. }
    28.    
    29. void WriteIListFloats( IList<float> data )
    30. {
    31.     for( int i = 0; i < data.Count; i++ )
    32.     {
    33.         data[i] = Random.value;
    34.     }
    35. }


    Apoligies for the poor quality of the graph images.

    Purple: TestFloats()
    Red: TestVirtualFloats()
    Blue: TestFloatsAsIList()

    Any idea why there's such a penalty iterating a float[] as an IList? My own implementation of IList encurs a minimal penalty, and is dead simple - overloading [] to iterate in a shared buffer at an offset. Exception handling maybe ( which I do not do )?

    Cheers,

    Gregzo
     

    Attached Files:

    Last edited: Nov 29, 2013
  9. half_voxel

    half_voxel

    Joined:
    Oct 20, 2007
    Posts:
    978
    First, IList is an interface, which means every call to this[index] (what's get's called when you request some item in the list) will be a virtual call. Virtual calls are slower than normal calls, and even slower than inlined calls. Furthermore when calling a function to return the item, a copy of the item needs to be made. In the normal case with arrays, it would just operate directly on the memory in the array. This overhead is not large since a float is only 4 bytes, but it is non-zero.

    Normal arrays have all logic inlined and I wouldn't be surprised if there are extra optimizations just for arrays.

    PS: There is some text I cannot read in those images.
     
  10. gregzo

    gregzo

    Joined:
    Dec 17, 2011
    Posts:
    795
    Hi TowerOfBricks,

    Sorry for the pic quality. Horizontal axis is array size ( 1 - 50000 ), vertical is number of ticks on my machine.

    I was expecting some overhead from interfacing, what I do not understand is why when casting a float[] to IList, the overhead when iterating is huge compared with my own implementation of IList. Close to 3 times slower...

    This is annoying because ideally, I'd like to be able to use the same functions to process my pooled floats and normal float arrays, hence the IList implementation. It works, but 3 times slower for native arrays is not really acceptable.
     
  11. Nidre

    Nidre

    Joined:
    Dec 12, 2012
    Posts:
    22
    Hello,

    I was looking for a way to find out how efficient my allocations are. I am currently working on a mobile title and i am not sure if i am trying to over optimize or not.

    Can any shed a light on the following questions :
    (I know they differ greatly depending on game, platform etc..etc.. just asking for general insight or experience.[Need Mobile info])

    - How much free heap you usually have or prefer to have after filling your pool ?
    - How much heap do you allocate each frame or second ?
     
  12. Grhyll

    Grhyll

    Joined:
    Oct 15, 2012
    Posts:
    119
    Hi!

    I am currently investigating in those pesky GC alloc, which I didn't even consider before today, in order to optimize my mobile game, and I have some questions for anyone would know something:
    - ParticleSystem::Stop and Clear seem to alloc some memory, is that normal? On some systems in my scene, it takes 184B each per system, leading to 1.4KB in my frame.
    - That 1.4KB seems to be a lot (although it is not on each frame), is it? (I'd like to support quite old mobile devices.)

    I may have more questions soon, but it is incredibly slow to profile allocs (unless there's a more convenient way than Profiler.BeginSample?)...
     
  13. BornGodsGame

    BornGodsGame

    Joined:
    Jun 28, 2014
    Posts:
    587
    From a more big picture opinion. This is one of the reasons why frankenstein games do not work, especially for non-programmers. I bought a quick $5 item one time, then spent half a day trying to make it work without causing a GC every 2 mins. About an hour into it, I realized I would have been better off just doing it myself.
     
  14. ratking

    ratking

    Joined:
    Feb 24, 2010
    Posts:
    350
    Frankenstein games? What?
     
  15. Neoptolemus

    Neoptolemus

    Joined:
    Jul 5, 2014
    Posts:
    52
    I think he means games constructed by buying and stitching together systems bought from the store, rather than personally tailored for your own needs.

    Certainly, it is harder to judge the quality of such things from sight alone than it is to judge art assets. The system may work fine, but is it performant? Does it have bugs? How will it fit with your existing code? These things are difficult to figure out before you buy, which is why I would never buy pre-made systems unless they were cheap and I wanted to see how they achieved a particular effect.
     
    ratking likes this.
  16. delinx32

    delinx32

    Joined:
    Apr 20, 2012
    Posts:
    417
    This thread got me looking at some garbage in my profiler, and this one popped up. Can anyone see where this method would be generating garbage? I certainly can't, I mean it's not doing anything other than calling Mathf.max, which doesn't show any garbage allocated:

    profile.PNG

    Code (CSharp):
    1.         public static float MaxComponent(this Vector3 vector)
    2.         {
    3.             return Mathf.Max(vector.x, vector.y, vector.z);
    4.         }
     
  17. bryantdrewjones

    bryantdrewjones

    Joined:
    Aug 29, 2011
    Posts:
    147
    Hey @delinx32,

    The code you pasted above is using this version of Mathf.Max():

    Code (CSharp):
    1. public static float Max(params float[] values);
    When you call that function, it's allocating an array to hold all of the parameters you pass into it. If you're trying to avoid allocations, you definitely do not want to be calling functions that accept a variable number of parameters like that :eek:
     
  18. Grhyll

    Grhyll

    Joined:
    Oct 15, 2012
    Posts:
    119
    Not totally sure you were answering me, but I'm talking about Unity's Particle System, nothing fancy bought on the asset store. This is why I find it surprising that basic functions like Stop and Clear allocate memory, and I was wondering where it could come from (and if I could avoid it somehow).
     
  19. BornGodsGame

    BornGodsGame

    Joined:
    Jun 28, 2014
    Posts:
    587
    Yeah, my bad for not explaining the term. Basically it is people who buy an assortment of random scripting systems and then just try to install them. For instance, an inventory system, an AI system, a UI system, a save game system... and then just try to add them to a game on top of each other. Many people just want easy-to-use stuff, but don´t realize many of them will crush performance in a finished game.