minetest / irrlicht

Minetest's fork of Irrlicht
Other
115 stars 87 forks source link

ISceneNode::removeChild() is slow #274

Closed Desour closed 10 months ago

Desour commented 10 months ago

I've profiled Wuzzy's shadow forest game, which has many enemies that shoot with particles on you, and causes 4fps or so on my machine, even on low view range. I've used RelWithDebInfo. Here's a screenshot of the Game::run() part: shadowforest_rlsdbg

Here's the implementation of ISceneNode::removeChild(), it iterates through all scene node childs: https://github.com/minetest/irrlicht/blob/fb4ee6ac930c16a63127427a817593d81e218349/include/ISceneNode.h#L290-L303

This needs to be fixed for better particle performance.

SmallJoker commented 10 months ago

The sorted std::set might perform better for lookups (binary search?). Or perhaps std::vector if the compiler is smart enough to parallelize the lookup code in std::find (e.g. by using SIMD).

Or move particles spawned by a particle spawner into a root ISceneNode's to shorten the child lookups.

appgurueu commented 10 months ago

The sorted std::set might perform better for loopkups (binary search?). Or perhaps std::vector if the compiler is smart enough to parallelize the lookup code in std::find (e.g. by using SIMD).

I don't think we need any sorting here. An unordered set would work, but is unnecessary. We have a doubly linked list and that allows O(1) removal - we just need the ISceneNode to store an iterator to its position in the list.

SmallJoker commented 10 months ago

@appgurueu How would you seamlessly update those iterators when one child is removed? Storing iterators is in general bad practice as they may get outdated. If not now, someone will surely forget to update them in a future change.

I think what slows down the code right now is that doubly-linked lists like std::list cannot be parallelized well due to the nature of pointer chains.

appgurueu commented 10 months ago

How would you seamlessly update those iterators when one child is removed?

That shouldn't be necessary, a doubly linked list doesn't invalidate iterators.

Storing iterators is in general bad practice as they may get outdated.

Doubly linked lists are special. Pretty much the only reason to use them is if you will be storing iterators to do element removal somewhere within the list in O(1) time.

If not now, someone will surely forget to update them in a future change.

I would have liked to believe that this would be well isolated to the addChild and removeChild methods, but ASan seems to be proving me wrong.

Desour commented 10 months ago

If in the future we want to replace the containers again or so, please note that iterating through also needs to be fast, because of functions like OnAnimate and OnRegisterSceneNode, which currently take about 1/8 client step cpu time that they mostly spend on iterating.