-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Improve Performance of Entities::alloc_at #18054
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
We should start by adding a benchmark specifically stressing this. The exact pattern from the user of "spawn 100k entities, despawn most of them, try spawning more" is really suitable for creating a microbenchmark that we can use to measure regressions against this. |
I'm on it! |
Aha! This code, where we're spawning specific entities, seemed highly unusual to me. With the advent of the retained rendering world, this shouldn't ever be needed. Going up the call stack, the offending methods only look to be called by |
I think that the right fix immediately is going to be to get some form of 18035 merged. Long-term, I think we should just remove this functionality completely, rather than risk slowing down any of the much more important paths. |
100% agree here. IDK where alloc_at would really be necessary. I'll get some benchmarks in shortly and defer to you for the solution. |
I believe that if the use cases in the renderer are removed all current motivating use cases will be gone. However the ability to claim a range of entities that remains invalid until they are made valid is the type of thing that might come up again (and in those cases, being able to "spawn into" the entities to make them valid does make sense). That being said, I'm not opposed to removing this now given its general "weirdness factor" and performance implications. No need to worry about a future that might never come. If we plan on removing support for this, we should also look into some of the "invalid checks" in Entities. Iirc we added additional checking to support this scenario, and we might be able to simplify / optimize some things. Worth digging up the PRs that introduced those changes. |
Agreed here: I think that there's a decent chance that we end up wanting this functionality in some form for niche, performance sensitive things. Most of the applications I've seen for entity partitioning seem like they overlap heavily with multi-world scenarios, and given the relative intuitiveness of multi-world I'd prefer to push in that direction for now. |
Music to my ears. After 0.16, I am curious what y'all would think of #17937 too, but I don't want to get ahead of myself.
If I were personally designing this feature from scratch, I would reach for either entity partitioning/multi-world OR some system to disable or mark as invalid an entity without despawning it. That would keep entity allocation fast and simple and may cut down on archetype changes. Just a thought. But like y'all said, we should cross that bridge when/if we come to it. |
As a note, we have entity disabling coming in Bevy 0.16, and live on main :) |
# Objective Based on #18054, this PR builds on #18035 to deprecate: - `Commands::insert_or_spawn_batch` - `Entities::alloc_at_without_replacement` - `Entities::alloc_at` - `World::insert_or_spawn_batch` - `World::insert_or_spawn_batch_with_caller` ## Testing Just deprecation, so no new tests. Note that as of writing #18035 is still under testing and review. ## Open Questions - [x] Should `entity::AllocAtWithoutReplacement` be deprecated? It is internal and only used in `Entities::alloc_at_without_replacement`. **EDIT:** Now deprecated. ## Migration Guide The following functions have been deprecated: - `Commands::insert_or_spawn_batch` - `World::insert_or_spawn_batch` - `World::insert_or_spawn_batch_with_caller` These functions, when used incorrectly, can cause major performance problems and are generally viewed as anti-patterns and foot guns. These are planned to be removed altogether in 0.17. Instead of these functions consider doing one of the following: Option A) Instead of despawing entities and re-spawning them at a particular id, insert the new `Disabled` component without despawning the entity, and use `try_insert_batch` or `insert_batch` and remove `Disabled` instead of re-spawning it. Option B) Instead of giving special meaning to an entity id, simply use `spawn_batch` and ensure entity references are valid when despawning. --------- Co-authored-by: JaySpruce <[email protected]> Co-authored-by: Alice Cecile <[email protected]>
# Objective Based on and closes #18054, this PR builds on #18035 and #18147 to remove: - `Commands::insert_or_spawn_batch` - `Entities::alloc_at_without_replacement` - `Entities::alloc_at` - `entity::AllocAtWithoutReplacement` - `World::insert_or_spawn_batch` - `World::insert_or_spawn_batch_with_caller` ## Testing Just removing unused, deprecated code, so no new tests. Note that as of writing, #18035 is still under testing and review. ## Future Work Per [this](#18054 (comment)) comment on #18054, there may be additional performance improvements possible to the entity allocator now that `alloc_at` no longer is supported. At a glance, I don't see anything obvious to improve, but it may be worth further investigation in the future. --------- Co-authored-by: JaySpruce <[email protected]>
Entities::alloc_at
andalloc_at_without_replacement
currently have performance issues. See this conversation for the full background.When and why is it slow?
Both these functions currently have a linear search through the
pending
list to try to find and remove the requested entity. When there are lots ofpending
entities, this is very slow.See also the source.
Practical Impact
In the conversation mentioned above, a user was spawning waves of sprite projectiles. Each wave was ~90,000 entities and was spawned and desawned over 8 seconds, repeating for each wave. This lead to measurable frame drops because, on the second wave, the render world was using
alloc_at_without_replacement
with 90,000 entities in the pending list. For massive, wave oriented games, this is a big deal.Solution
Add a field to
EntityMeta
that stores the index of the entity in thepending
list if it is fee. This will remove the linear search and fix the problem.Downside:
EntityMeta
will be bigger, so less of them can fit in cache. This is relevant to lots of very hot paths.Alternatives
We could try to keep the
pending
list small by not storing tail entities. For example, if there are 16 entries inmeta
and entities of index 8-15 are in the pending list, we could remove them since their pending nature could be implied by shorteningmeta
. But this gives extra overhead and can be ruined by keeping just one high-index entity around.We could also try to pack the index of the entity in the
pending
list into some other fields ofEntityMeta
. Maybetable_row
or something, but that looses/invalidates other information about the entity. If anyone has a way of doing this without dropping some information, this would be an ideal solution.Finally, we could sort the pending list after
Entities::flush
. This could let us change the linear search to a binary search, but keeping the sorted invariance may have additional overhead.The text was updated successfully, but these errors were encountered: