Skip to content

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

Closed
ElliottjPierce opened this issue Feb 26, 2025 · 9 comments · Fixed by #18148
Closed

Improve Performance of Entities::alloc_at #18054

ElliottjPierce opened this issue Feb 26, 2025 · 9 comments · Fixed by #18148
Labels
A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Design This issue requires design work to think about how it would best be accomplished S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! X-Contentious There are nontrivial implications that should be thought through

Comments

@ElliottjPierce
Copy link
Contributor

Entities::alloc_at and alloc_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 of pending 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 the pending 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 in meta and entities of index 8-15 are in the pending list, we could remove them since their pending nature could be implied by shortening meta. 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 of EntityMeta. Maybe table_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.

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Design This issue requires design work to think about how it would best be accomplished labels Feb 26, 2025
@alice-i-cecile
Copy link
Member

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.

@ElliottjPierce
Copy link
Contributor Author

We should start by adding a benchmark specifically stressing this.

I'm on it!

@alice-i-cecile
Copy link
Member

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 insert_or_spawn_batch, which #18035 removes our internal usage of.

@alice-i-cecile
Copy link
Member

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.

@ElliottjPierce
Copy link
Contributor Author

Long-term, I think we should just remove this functionality completely.

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.

@alice-i-cecile alice-i-cecile added S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! X-Controversial There is active debate or serious implications around merging this PR and removed S-Needs-Design This issue requires design work to think about how it would best be accomplished labels Feb 26, 2025
@alice-i-cecile alice-i-cecile added X-Contentious There are nontrivial implications that should be thought through S-Needs-Design This issue requires design work to think about how it would best be accomplished and removed X-Controversial There is active debate or serious implications around merging this PR labels Feb 27, 2025
@cart
Copy link
Member

cart commented Feb 27, 2025

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.

@alice-i-cecile
Copy link
Member

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).

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.

@ElliottjPierce
Copy link
Contributor Author

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.

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.

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.

@alice-i-cecile
Copy link
Member

As a note, we have entity disabling coming in Bevy 0.16, and live on main :)

github-merge-queue bot pushed a commit that referenced this issue Mar 6, 2025
# 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]>
github-merge-queue bot pushed a commit that referenced this issue May 5, 2025
# 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]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times S-Needs-Design This issue requires design work to think about how it would best be accomplished S-Ready-For-Implementation This issue is ready for an implementation PR. Go for it! X-Contentious There are nontrivial implications that should be thought through
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants