Doraku / DefaultEcs

Entity Component System framework aiming for syntax and usage simplicity with maximum performance for game development.
MIT No Attribution
658 stars 62 forks source link

Add way to shrink memory usage when removing entities #110

Closed Doraku closed 3 years ago

Doraku commented 3 years ago

@Doraku, I'm terribly sorry for interjecting into some other thread, but I don't want to spam new issues just for asking questions, and I couldn't find any forum with discussions.

Is there any way to shrink the World capacity back down? Let's say I created 150k entities in a world without capacity limitations, and then disposed all of them. It there any way to shrink the world's capacity back down and free up the unused memory?

Originally posted by @Exerionius in https://github.com/Doraku/DefaultEcs/issues/107#issuecomment-793017547

Hey, don't shy from creating a new issue even just for a question :) you can also use gitter or the new discussion feature of github.

Anyway I think this request actually warrant its own issue. Currently there's no api to do this but I can think of a way to do it with moderate to good result. Some internal arrays can be reduced without problem (components which are probably the biggest usage, entity set and map) but it would probably be hard to reduce the entity id mapping array (basically some int[] to point to the other sparse sets containing the actual interesting data). Worst case you create 150k entities, and remove all but the last one. Internally this entity has the id 149999 and we can't reduce the mapping arrays but we can clean everything else. This operation would also cost a lot and not something you should do often (maybe in a loading screen?) but even in the worst case you should get back the majority of the memory.

Now I am kinda interested in why you would want this? The main thing we are fighting against in c# is memory allocation and the GC. As you initialize your world and create entities, set components, the inner arrays will grow and stress the GC but once done the memory should stay flat. If you remove entities and create them back there is no need to grow the inner arrays again so it is much more usable a game loop. Shrinking it would mean you would pay for the growth again as you add entities and components.

Exerionius commented 3 years ago

Well, I was just playing around with how many entities I can spawn before I dip below 60 fps on my system, gradually spawning more and more. I was looking at the process performance in the ProcessExplorer just because I was curious, and that is when I discovered that after eating up 196 MiB of memory and removing all except one entities it was not freed up even with a manual call of GC.Collect() or World.Optimize().

It's a natural thought for me, as an enterprise dev, that unused resources should be freed up, but I guess you're right, in the scope of gamedev these unnecessary shrinks, growths and garbage collections might not be desirable in terms of performance. Usually memory is not an issue in games, gameloop speed is.

Doraku commented 3 years ago

I get were you are coming from, I have also mostly worked as a non gaming dev. For my game I use multiple scenes each with their own world so when I switch screen the previous world memory is automatically returned. Still this feature might be interesting since the inner array grow by a factor of 2 and might be greedy so even after loading your full world having a way to get back some memory would be useful. I will give it a go and see how much difference it does for the DefaultBoids sample (currently takes about 150Mo for me).

Doraku commented 3 years ago

So, I added TrimExcess methods on some DefaultEcs objects (and more importantly on the World type to apply it on all underlying objects). First test on DefaultBoids sample after full initialization show memory shrinking from 28Mo to 19Mo after a TrimExcess call, and 7Mo after disposing all entities (~70k) (all measure taken after a forced GC run). Still need to add some tests but it's looking ok :)

edit: just to clear things up btw, World.Optimize just reorder internal array so that when enumerating entities collection (set, map, multimap) and accessing their components you are always moving forward in memory, this help cpu cache but it doesn't reduce the used memory.