Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

object pooling in dots?

Discussion in 'Entity Component System' started by Cell-i-Zenit, May 9, 2020.

  1. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    Hi,

    I have millions of cubes in my game. Right now i spawn X cubes per frame, which keeps the fps high, but there will be some moments where i only need to show like 20 cubes. What do i do with the rest?

    Just keep them there, but move them somewhere where the player cant see them? Is disabling better (eg adding Disabled Tag to them?)

    What should be the correct way?
     
    Krajca likes this.
  2. Enzi

    Enzi

    Joined:
    Jan 28, 2013
    Posts:
    910
    I think you can just remove the sharedcomponent RenderMesh from them.

    I'm honestly not sure what the best way is, but if you use a remove query for the RenderMesh comp that has all the entities in it that are disabled in the current frame, performance should be still good.

    Some don't even pool in ECS, just spawn how many they need and destroy too. I spawn like 10k monsters and it's done in < 1ms. Depending on how fast your cubes scale up in a single frame it might be enough.
     
  3. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    As a starting point in DOTS don't do any pooling. Instantiation & destruction when done in batch is fast.

    Adding / removing Disabled component is a reasonable option to hide a bunch of entities of you want to maintian their state. You want to use EntityQuery base add /remove component to do this efficiently. (In batch as opposed to one entity at a time which is multiple magnitudes slower)
     
  4. Cell-i-Zenit

    Cell-i-Zenit

    Joined:
    Mar 11, 2016
    Posts:
    290
    What if i need to do this based on a simple condition (eg position > 10) ? Is there a way to extract a query out of a condition?
     
  5. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
    In my opinion, just check the condition inside the ForEach statement of your system. As far as I'm aware (would love to be shown otherwise), there's no faster way.

    If your condition only matches for 10% of the entities in your query, the other 90% will just slide past super fast.
     
  6. Opeth001

    Opeth001

    Joined:
    Jan 28, 2017
    Posts:
    1,078
    For this you need to set the component containing this value as SharedComponentData, this way you can still disabling them by query using filters.

    I already asked for a feature which will make this case easier ( hope they at least aware about it ) post
     
    elZach likes this.
  7. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
  8. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    I don't understand. When pooling, you know what you have all the time, you know the total of the objects, and you don't have to do operations to create/delete.

    I would like to see some details and in-depth performance statistics on pooling vs. DOTS-re/creation.
     
  9. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    Dots isn't actually creating or destroying anything. The data is just entries in an array. When you create a new entity it's just filling out data in an archetype. These aren't gameobjects with tons of initialization and setup code and memory to allocate. While there is a tiny overhead to entity management (adding it to lookup tables and such), any pooling system you write would most likely be slower than this by a lot, as you'd have to do logic to track what objects should be active, inactive, what pool the object is in, etc.. In short, ECS basically already has all the benefits of pooling without you needing to do anything.
     
    elZach, NotaNaN and Opeth001 like this.
  10. jonwah00

    jonwah00

    Joined:
    Jul 21, 2019
    Posts:
    36
    What about if you're dynamically creating RenderMesh components at runtime, and they should be frustrum culled, but they're not, because as far as I understand it the normal frustrum culling doesn't work on dynamic meshes yet?

    Even if it did work, if you had a game world where you were making terrain or a giant grid, wouldn't you be better off disabling or removing the RenderMesh components until you really needed them?
     
    Nyanpas likes this.
  11. Nyanpas

    Nyanpas

    Joined:
    Dec 29, 2016
    Posts:
    406
    I have that issue. I am creating meshes during runtime for a randomly generated game world, both instantiating models based on imported FBX, but also parametrically created from scripts. Currently I am not using the HybridRenderer because of a few issues that must be fixed first (currently using custom mesh-combine/drawinstance-methods), but I plan on moving to that if they get fixed.
     
    elZach likes this.
  12. Cynicat

    Cynicat

    Joined:
    Jun 12, 2013
    Posts:
    290
    Sadly i'm not familiar with the hybrid renderer's internals yet, but yeah if you have external reasons to pool for your own logic it's different. I'm just saying that ECS by it's design doesn't create much overhead for creation and destruction, if you have lots of initialization/generation logic in your own code, disabling an entity would probably be a better option. Just don't do it to small/simple entities, it's not worth it.
     
  13. PublicEnumE

    PublicEnumE

    Joined:
    Feb 3, 2019
    Posts:
    729
    This is a good point, but unity still needs to create/chunks to hold those entities. There is a cost for that (though minor compared to creating/destroying a GameObject).
     
  14. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    Since all chunks are the same size we do pooling of the chunks internally. This means we can pool across any archetype... So allocating a new chunk internally is practically free.

    The only real cost in instantiation is filling out the cloned data. But thats mostly memcpy & patching entity references.

    In any case, i think the first instinct with unity.entities should be to do no pooling at all.
    Then measure and see what the specific perf issue you hit with the simplest approach.
     
    Rockaso, SenseEater, Cynicat and 2 others like this.
  15. bsterling250DI

    bsterling250DI

    Joined:
    Sep 25, 2014
    Posts:
    78
    @Joachim_Ante I'm really surprised to read not to worry about allocations.

    Even ignoring the garbage collector and the potential hickups which are getting better, but used to be an issue, what would you say to a project that is already feeling memory pressure and already working with the most optimized textures and mesh and resource memory they can, to the point that even seeing less low-memory warnings on device by eliminating passing strings around and using foreach loops on dictionaries because of the extra allocations, or if you're developing on console where you need to be specific with every allocation.
     
  16. Joachim_Ante

    Joachim_Ante

    Unity Technologies

    Joined:
    Mar 16, 2005
    Posts:
    5,203
    I am not saying pooling as a general strategy when using game objects or other things that allocate GC memory is not useful. I am saying that pooling instantiated entities in Unity.Entities has no real merit because we solved the problem at the root by having pooling of chunks internally + very fast instantiation when done in batch.

    There are no GC allocations if you take the recommended fast paths. Specifically if you only have shared components + struct IComponentData. Then instantiating entities will allocate zero GC memory.

    As an example. A few years back in the Nordeus large battle field demo. We were spawning 10.000 arrows per frame. That was not doing any pooling at all...


    class IComponentData does exist, if you actually use them, then yes there is GC memory allocations and that means it would be useful to do pooling. But the point here is, if you care about performance & GC memory pressure, then don't use class components in your game at all for this and many other reasons. They can be convenient in some cases but they are not performance by default....


    Hence the recommendation when working with entities is that you use struct components + don't do pooling as a default approach. And measure specific cases where thats not enough.
     
    MagiJedi, JesOb, desertGhost_ and 2 others like this.
  17. bsterling250DI

    bsterling250DI

    Joined:
    Sep 25, 2014
    Posts:
    78
    @Joachim_Ante Thanks for clarifying. Just so I can explain that to my coworkers who are evaluating ecs, how much space is taken by the pooling of those chunks or how is that determined? like there has to be some number somewhere of how much memory usage the system takes, like total footprint? Is there a relationship on how much memory is allocated to how many systems you have or components you have or entities? While i remember the demo, i'm looking at a project that at one point had a 30% crash rate on older devices and some really annoying low-memory issues which was horrendous. we spent a lot of time optimizing, using the memory profiler to shrink our assets, we share as many resources as we can, we've found other areas where we can optimize like using StringBuilder instead of concatenating strings, using C-style for loops instead of foreach on collections. So you can imagine the first question my technical director is going to ask when i say "it's pooled internally by unity" is going to be "but how?"

    you seem to be specifying no "GC" allocations, but what about "non-GC" allocations? do those even exist? does that mean they are all allocated on the stack instead of the managed heap? or managed internally by native code?
     
    elZach and Opeth001 like this.
  18. BenzzzX

    BenzzzX

    Joined:
    Jun 4, 2018
    Posts:
    14
    There's "non-GC"(Native) allocations when creating chunks(not creating entities) and chunks are pooled internally by Unity.Entities as @Joachim_Ante mentioned.