-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Bevy version
0.16.1
What you did
As I find early return expressions to be a little more ergonomic than writing extra systems for conditional system execution, I was wondering on performance caveats.
I made 10,000 entities with transform and 10,000 without and let systems using filtered queries to both run (or better: I rejected their run through conditions). I used a resource holding a bool. This is either passed to the system directly for an immediate return or it is passed to a condition check system that has the same simple if expression checking this struct's content bool. I let the app with conditional system and a separate one with internal return run for 10 sec each and compared the methods of conditionally running the systems.
The difference is significant in lightweight systems that are returned early/not run with 12 µs vs 10 µs favouring run_if. As a redittor pointed out, however, the cost may be higher when scheduling several tasks for multitasking is involved where creating a task only for cancelling it should have a high overhead.
When I checked this specifically by adding three dummy systems, however, things got confusing. When I added three systems doing nothing but calling the same queries and then returning, we get about 24 µs for the early return vs 28->90 µs for conditional execution.
What went wrong
I would assume that the run_if implementation should be just as efficient if not more than an early return in systems.
Additional information
Wild theory
Is the schedule affected by the conditional execution? I would assume this would get more expensive in any realistic app with dozens or more systems, early returns would always be the way to go.
If this is true, conditional systems could perhaps be rewired to function as interface for an early return rather than inducing a rescheduling?
Log
Single system
---- dev_tools::bench::bench_if_vs_early_return stdout ----
Early return frame time: 12.12 µs
Conditional system frame time: 9.93 µs
With three dummy systems
Run 1
---- dev_tools::bench::bench_if_vs_early_return stdout ----
Early return frame time: 25.79 µs
Conditional system frame time: 28.68 µs
Run 2
---- dev_tools::bench::bench_if_vs_early_return stdout ----
Early return frame time: 24.65 µs
Conditional system frame time: 96.84 µs
Run 3
---- dev_tools::bench::bench_if_vs_early_return stdout ----
Early return frame time: 24.93 µs
Conditional system frame time: 63.38 µs
Benchmark
use bevy::prelude::*;
#[test]
fn bench_if_vs_early_return() {
use std::time::Duration;
const BENCH_SECS: u64 = 10;
const WITH_DUMMIES: bool = true;
#[derive(Resource, Reflect, Debug, Default)]
#[reflect(Resource)]
struct Toggle(bool);
fn dummy1(
q1: Query<Entity, With<Transform>>,
q2: Query<Entity, Without<Transform>>,
toggle: Res<Toggle>,
) {
for e1 in q1 {
for e2 in q2 {
return;
}
}
}
fn dummy2(
q1: Query<Entity, With<Transform>>,
q2: Query<Entity, Without<Transform>>,
toggle: Res<Toggle>,
) {
for e1 in q1 {
for e2 in q2 {
return;
}
}
}
fn dummy3(
q1: Query<Entity, With<Transform>>,
q2: Query<Entity, Without<Transform>>,
toggle: Res<Toggle>,
) {
for e1 in q1 {
for e2 in q2 {
return;
}
}
}
fn system_internal_if(
q1: Query<Entity, With<Transform>>,
q2: Query<Entity, Without<Transform>>,
toggle: Res<Toggle>,
) {
if toggle.0 {
for e1 in q1 {
for e2 in q2 {
println!("{} {}", e1, e2);
}
}
}
}
fn run_condition(toggle: Res<Toggle>) -> bool {
toggle.0
}
fn system_conditional(
q1: Query<Entity, With<Transform>>,
q2: Query<Entity, Without<Transform>>,
) {
for e1 in q1 {
for e2 in q2 {
println!("{} {}", e1, e2);
}
}
}
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.init_resource::<Toggle>();
app.add_systems(Update, system_internal_if);
if WITH_DUMMIES {
app.add_systems(Update, (dummy1, dummy2, dummy3));
}
for _ in 0..10000 {
app.world_mut().spawn(Transform::default());
}
for _ in 0..10000 {
app.world_mut().spawn_empty();
}
let mut time = Duration::from_secs(0);
let mut frame_count = 0;
while time < Duration::from_secs(BENCH_SECS) {
app.update();
frame_count += 1;
time += app.world().resource::<Time>().delta();
}
println!(
"Early return frame time: {:.2} µs",
time.as_secs_f32() / frame_count as f32 * 1000000.0
);
let mut app = App::new();
app.add_plugins(MinimalPlugins);
app.init_resource::<Toggle>();
app.add_systems(Update, system_conditional.run_if(run_condition));
if WITH_DUMMIES {
app.add_systems(Update, (dummy1, dummy2, dummy3));
}
for _ in 0..10000 {
app.world_mut().spawn(Transform::default());
}
for _ in 0..10000 {
app.world_mut().spawn_empty();
}
let mut time = Duration::from_secs(0);
let mut frame_count = 0;
while time < Duration::from_secs(BENCH_SECS) {
app.update();
frame_count += 1;
time += app.world().resource::<Time>().delta();
}
println!(
"Conditional system frame time: {:.2} µs",
time.as_secs_f32() / frame_count as f32 * 1000000.0
);
}