Bevy ECS: Parallelization Limits When Systems Share Mutable Component Access
0 reputation · 30 Aug 2020, 07:50 UTC
0 reputation · 30 Aug 2020, 07:50 UTC
Bevy's ECS executes systems in parallel by default, but any two systems that request mutable access to the same component type are forced into sequential execution unless explicit ordering constraints (.before() or .after()) are declared. This behavior is documented in the Schedule and System execution model.
Determine the practical threshold at which the scheduler's conservative conflict detection begins to materially reduce frame-time parallelism in a system set that processes high entity counts (10,000+ entities) with overlapping component queries.
The scheduler does not currently expose a diagnostic API that reports how many systems were serialized due to inferred conflicts versus explicit ordering. Profiling tools show total frame time but do not attribute serialization overhead to specific component-access patterns. Version 0.14's SystemSet hierarchy and SystemConfigs modifiers are assumed.
Query<&mut T, With> and Query<&mut T, With> run in parallel when A and B are disjoint archetypes, or does the type-level conflict check ignore filters?Bevy builds a conflict graph at app startup based solely on component type access (read/write). The current public API does not expose this graph, nor is there a diagnostic endpoint that reports how many systems were serialized because of inferred conflicts. In practice, you can infer serialization indirectly by:
.before() or .after() constraints and measuring the change in frame time.bevy::diagnostic::FrameTimeDiagnosticsPlugin and looking for scheduler warnings in the debug log (e.g., "System X conflicts with Y, running sequentially").So, answer to Q1: No, there is currently no supported method to query the conflict graph directly.
When two systems request mutable access to the same component type, the scheduler considers them conflicting regardless of any With or Without filters. The type‑level conflict check ignores the specific archetypes a system will hit. Therefore, Query<&mut T, With> and Query<&mut T, With> will be serialized unless you add an explicit ordering constraint.
In a high‑entity scenario (10 000+ entities) this can quickly become a bottleneck: each conflicting pair introduces a serialization point that the scheduler can’t hide behind parallelism. The practical threshold is roughly the number of conflicting systems that exceeds the available CPU cores. When conflict density grows beyond the core count, the scheduler’s conservative policy will serialise enough systems to keep the core count saturated, and you’ll see a linear increase in the serialized portion of the frame time.
The recommended pattern is to decompose a monolithic mutable component into smaller, orthogonal sub‑components. Each system then mutates only the sub‑component it cares about. Because the scheduler’s conflict graph is built per component type, this eliminates the type‑level conflict without needing .before/.after sync points.
#[derive(Component)]
struct Position { x: f32, y: f32 }
#[derive(Component)]
struct Velocity { vx: f32, vy: f32 }
fn move_system(mut query: Query<(&mut Position, &Velocity)>) {
for (mut pos, vel) in query.iter_mut() {
pos.x += vel.vx;
pos.y += vel.vy;
}
}
fn update_velocity_system(mut query: Query<&mut Velocity>) {
for mut vel in query.iter_mut() {
// modify velocity independently
}
}
With this split, move_system mutates Position while update_velocity_system mutates Velocity. The scheduler sees no conflict and runs them in parallel. Keep an eye on cache locality: over‑splitting can hurt performance if the data is frequently accessed together.
With tags.FrameTimeDiagnosticsPlugin and set log level to DEBUG..before() between the systems and confirm that the conflict warning disappears and frame time drops.By following these steps you can determine the practical threshold for serialization in your specific workload and apply the split‑component pattern to recover parallelism.
To refine the recommendation, could you share the number of CPU cores you expect to use for the target hardware?
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.