Inline world get (#2520)

# Objective

While looking at the code of `World`, I noticed two basic functions (`get` and `get_mut`) that are probably called a lot and with simple code that are not `inline`

## Solution

- Add benchmark to check impact
- Add `#[inline]`


```
group                                            this pr                                main
-----                                            ----                                   ----
world_entity/50000_entities                      1.00   115.9±11.90µs        ? ?/sec    1.71   198.5±29.54µs        ? ?/sec
world_get/50000_entities_SparseSet               1.00   409.9±46.96µs        ? ?/sec    1.18   483.5±36.41µs        ? ?/sec
world_get/50000_entities_Table                   1.00   391.3±29.83µs        ? ?/sec    1.16   455.6±57.85µs        ? ?/sec
world_query_for_each/50000_entities_SparseSet    1.02   121.3±18.36µs        ? ?/sec    1.00   119.4±13.88µs        ? ?/sec
world_query_for_each/50000_entities_Table        1.03     13.8±0.96µs        ? ?/sec    1.00     13.3±0.54µs        ? ?/sec
world_query_get/50000_entities_SparseSet         1.00   666.9±54.36µs        ? ?/sec    1.03   687.1±57.77µs        ? ?/sec
world_query_get/50000_entities_Table             1.01   584.4±55.12µs        ? ?/sec    1.00   576.3±36.13µs        ? ?/sec
world_query_iter/50000_entities_SparseSet        1.01   169.7±19.50µs        ? ?/sec    1.00   168.6±32.56µs        ? ?/sec
world_query_iter/50000_entities_Table            1.00     26.2±1.38µs        ? ?/sec    1.91     50.0±4.40µs        ? ?/sec
```

I didn't add benchmarks for the mutable path but I don't see how it could hurt to make it inline too...
This commit is contained in:
François 2021-07-27 23:19:26 +00:00
parent 6d6bc2a8b4
commit 234b2efa71
3 changed files with 168 additions and 0 deletions

View file

@ -21,6 +21,11 @@ name = "commands"
path = "benches/bevy_ecs/commands.rs" path = "benches/bevy_ecs/commands.rs"
harness = false harness = false
[[bench]]
name = "world_get"
path = "benches/bevy_ecs/world_get.rs"
harness = false
[[bench]] [[bench]]
name = "iter" name = "iter"
path = "benches/bevy_tasks/iter.rs" path = "benches/bevy_tasks/iter.rs"

View file

@ -0,0 +1,161 @@
use bevy::ecs::{
component::{ComponentDescriptor, StorageType},
entity::Entity,
world::World,
};
use criterion::{black_box, criterion_group, criterion_main, Criterion};
criterion_group!(
benches,
world_entity,
world_get,
world_query_get,
world_query_iter,
world_query_for_each,
);
criterion_main!(benches);
struct A(f32);
const RANGE: std::ops::Range<u32> = 5..6;
fn setup(entity_count: u32, storage: StorageType) -> World {
let mut world = World::default();
world
.register_component(ComponentDescriptor::new::<A>(storage))
.unwrap();
world.spawn_batch((0..entity_count).map(|_| (A(0.0),)));
world
}
fn world_entity(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("world_entity");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE.map(|i| i * 10_000) {
group.bench_function(format!("{}_entities", entity_count), |bencher| {
let world = setup(entity_count, StorageType::Table);
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
black_box(world.entity(entity));
}
});
});
}
group.finish();
}
fn world_get(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("world_get");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let world = setup(entity_count, storage);
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(world.get::<A>(entity).is_some());
}
});
},
);
}
}
group.finish();
}
fn world_query_get(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("world_query_get");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let mut world = setup(entity_count, storage);
let mut query = world.query::<&A>();
bencher.iter(|| {
for i in 0..entity_count {
let entity = Entity::new(i);
assert!(query.get(&world, entity).is_ok());
}
});
},
);
}
}
group.finish();
}
fn world_query_iter(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("world_query_iter");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let mut world = setup(entity_count, storage);
let mut query = world.query::<&A>();
bencher.iter(|| {
let mut count = 0;
for comp in query.iter(&world) {
black_box(comp);
count += 1;
}
assert_eq!(black_box(count), entity_count);
});
},
);
}
}
group.finish();
}
fn world_query_for_each(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("world_query_for_each");
group.warm_up_time(std::time::Duration::from_millis(500));
group.measurement_time(std::time::Duration::from_secs(4));
for entity_count in RANGE.map(|i| i * 10_000) {
for storage in [StorageType::Table, StorageType::SparseSet] {
group.bench_function(
format!("{}_entities_{:?}", entity_count, storage),
|bencher| {
let mut world = setup(entity_count, storage);
let mut query = world.query::<&A>();
bencher.iter(|| {
let mut count = 0;
query.for_each(&world, |comp| {
black_box(comp);
count += 1;
});
assert_eq!(black_box(count), entity_count);
});
},
);
}
}
group.finish();
}

View file

@ -344,6 +344,7 @@ impl Entities {
/// Access the location storage of an entity. /// Access the location storage of an entity.
/// ///
/// Must not be called on pending entities. /// Must not be called on pending entities.
#[inline]
pub fn get_mut(&mut self, entity: Entity) -> Option<&mut EntityLocation> { pub fn get_mut(&mut self, entity: Entity) -> Option<&mut EntityLocation> {
let meta = &mut self.meta[entity.id as usize]; let meta = &mut self.meta[entity.id as usize];
if meta.generation == entity.generation { if meta.generation == entity.generation {
@ -354,6 +355,7 @@ impl Entities {
} }
/// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities. /// Returns `Ok(Location { archetype: 0, index: undefined })` for pending entities.
#[inline]
pub fn get(&self, entity: Entity) -> Option<EntityLocation> { pub fn get(&self, entity: Entity) -> Option<EntityLocation> {
if (entity.id as usize) < self.meta.len() { if (entity.id as usize) < self.meta.len() {
let meta = &self.meta[entity.id as usize]; let meta = &self.meta[entity.id as usize];