Bevy Version: | 0.9 | (outdated!) |
---|
As this page is outdated, please refer to Bevy's official migration guides while reading, to cover the differences: 0.9 to 0.10, 0.10 to 0.11, 0.11 to 0.12, 0.12 to 0.13, 0.13 to 0.14.
I apologize for the inconvenience. I will update the page as soon as I find the time.
Component Storage (Table/Sparse-Set)
Bevy ECS provides two different ways of storing data: tables and sparse sets. The two storage kinds offer different performance characteristics.
The kind of storage to be used can be chosen per component
type. When you derive the Component
trait, you can
specify it. The default, if unspecified, is table storage. You can have
components with a mixture of different storage kinds on the same entity.
The rest of this page is dedicated to explaining the performance trade-offs and why you might want to choose one storage kind vs. the other.
/// Component for entities that can cast magic spells
#[derive(Component)] // Use the default table storage
struct Mana {
mana: f32,
}
/// Component for enemies that currently "see" the player
/// Every frame, add/remove to entities based on visibility
/// (use sparse-set storage due to frequent add/remove)
#[derive(Component)]
#[component(storage = "SparseSet")]
struct CanSeePlayer;
/// Component for entities that are currently taking bleed damage
/// Add to entities to apply bleed effect, remove when done
/// (use sparse-set storage to not fragment tables,
/// as this is a "temporary effect")
#[derive(Component)]
#[component(storage = "SparseSet")]
struct Bleeding {
damage_rate: f32,
}
Table Storage
Table storage is optimized for fast query iteration. If the way you usually use a specific component type is to iterate over its data across many entities, this will offer the best performance.
However, adding/removing table components to existing entities is a relatively slow operation. It requires copying the data of all table components for the entity to a different location in memory.
It's OK if you have to do this sometimes, but if you are likely to add/remove a component very frequently, you might want to switch that component type to sparse-set storage.
You can see why table storage was chosen as Bevy's default. Most component types are rarely added/removed in practice. You typically spawn entities with all the components they should have, and then access the data via queries, usually every frame. Sometimes you might add or remove a component to change an entity's behavior, but probably not nearly as often, or every frame.
Sparse-Set Storage
Sparse-Set storage is optimized for fast adding/removing of a component to existing entities, at the cost of slower querying. It can be more efficient for components that you would like to add/remove very frequently.
An example of this might be a marker component indicating whether an enemy is currently aware of the player. You might want to have such a component type, so that you can easily use a query filter to find all the enemies that are currently tracking the player. However, this is something that can change every frame, as enemies or the player move around the game level. If you add/remove this component every time the visibility status changed, that's a lot of additions and removals.
You can see that situations like these are more niche and do not apply to most typical component types. Treat sparse-set storage as a potential optimization you could try in specific circumstances.
Even in situations like the example above, it might not be a performance win. Everything depends on your application's unique usage patterns. You have to measure and try.
Table Fragmentation
Furthermore, the actual memory layout of the "tables" depends on the set of all table components that each of your entities has.
ECS queries perform best when many of the entities they match have the same overall set of components.
Having a large number of entities, that all have the same component types, is very efficient in terms of data access performance. Having diverse entities with a varied mixture of different component types, means that their data will be fragmented in memory and be less efficient to access.
Sparse-Set components do not affect the memory layout of tables. Hence, components that are only used on a few entities or as a "temporary effect", might also be good candidates for sparse-set storage. That way they don't fragment the memory of the other (table) components. Systems that do not care about these components will be completely unaffected by them existing.
Overall Advice
While this page describes the general performance characteristics and gives some guidelines, you often cannot know if something improves performance without benchmarking.
When your game grows complex enough and you have something to benchmark, you could try to apply sparse-set storage to situations where it might make sense, as described above, and see how it affects your results.