From 3554be6953848c60a5cc831f60cb35cde92a3ff4 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sun, 26 Dec 2021 23:07:35 +0100 Subject: [PATCH 1/3] Add insertion/removal benchmark around a steady state of attached components. --- benches/bench.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/benches/bench.rs b/benches/bench.rs index d30a98a7..fed445d7 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -85,6 +85,21 @@ fn insert(b: &mut Bencher) { }); } +fn insert_remove(b: &mut Bencher) { + let mut world = World::new(); + let entities = world + .spawn_batch((0..1_000).map(|_| (Position(0.0), Velocity(0.0)))) + .collect::>(); + let mut entities = entities.iter().cycle(); + b.iter(|| { + let e = *entities.next().unwrap(); + world.remove_one::(e).unwrap(); + world.insert_one(e, true).unwrap(); + world.remove_one::(e).unwrap(); + world.insert_one(e, Velocity(0.0)).unwrap(); + }); +} + fn iterate_100k(b: &mut Bencher) { let mut world = World::new(); for i in 0..100_000 { @@ -250,6 +265,7 @@ benchmark_group!( spawn_batch, remove, insert, + insert_remove, iterate_100k, iterate_mut_100k, iterate_uncached_100_by_50, From e17fbfdcfcafce2338fd830cda76d6e70ed5d708 Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sun, 26 Dec 2021 22:17:47 +0100 Subject: [PATCH 2/3] Use a single hash table for compoment removal edges to improve spatial locality. --- src/archetype.rs | 4 ---- src/world.rs | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/archetype.rs b/src/archetype.rs index 6e5c39b4..3939566c 100644 --- a/src/archetype.rs +++ b/src/archetype.rs @@ -31,9 +31,6 @@ pub struct Archetype { entities: Box<[u32]>, /// One allocation per type, in the same order as `types` data: Box<[Data]>, - /// Maps static bundle types to the archetype that an entity from this archetype is moved to - /// after removing the components from that bundle. - pub(crate) remove_edges: TypeIdMap, } impl Archetype { @@ -70,7 +67,6 @@ impl Archetype { storage: NonNull::new(max_align as *mut u8).unwrap(), }) .collect(), - remove_edges: HashMap::default(), } } diff --git a/src/world.rs b/src/world.rs index 71e8ee4a..ab90017c 100644 --- a/src/world.rs +++ b/src/world.rs @@ -9,6 +9,7 @@ use crate::alloc::{vec, vec::Vec}; use core::any::TypeId; use core::borrow::Borrow; use core::convert::TryFrom; +use core::hash::{BuildHasherDefault, Hasher}; use core::marker::PhantomData; use spin::Mutex; @@ -50,6 +51,9 @@ pub struct World { archetypes: ArchetypeSet, /// Maps statically-typed bundle types to archetypes bundle_to_archetype: TypeIdMap, + /// Maps source archetype and static bundle types to the archetype that an entity is moved to + /// after removing the components from that bundle. + remove_edges: IndexTypeIdMap, id: u64, } @@ -69,6 +73,7 @@ impl World { entities: Entities::default(), archetypes: ArchetypeSet::new(), bundle_to_archetype: HashMap::default(), + remove_edges: HashMap::default(), id, } } @@ -653,6 +658,7 @@ impl World { // Gather current metadata let loc = self.entities.get_mut(entity)?; + let old_archetype = loc.archetype; let old_index = loc.index; let source_arch = &self.archetypes.archetypes[loc.archetype as usize]; @@ -662,7 +668,7 @@ impl World { }; // Find the target archetype ID - let target = match source_arch.remove_edges.get(&TypeId::of::()) { + let target = match self.remove_edges.get(&(old_archetype, TypeId::of::())) { Some(&x) => x, None => { let removed = T::with_static_ids(|ids| ids.iter().copied().collect::>()); @@ -674,9 +680,8 @@ impl World { .collect::>(); let elements = info.iter().map(|x| x.id()).collect::>(); let index = self.archetypes.get(&*elements, move || info); - self.archetypes.archetypes[loc.archetype as usize] - .remove_edges - .insert(TypeId::of::(), index); + self.remove_edges + .insert((old_archetype, TypeId::of::()), index); index } }; @@ -1245,6 +1250,29 @@ struct InsertTarget { index: u32, } +type IndexTypeIdMap = HashMap<(u32, TypeId), V, BuildHasherDefault>; + +#[derive(Default)] +struct IndexTypeIdHasher(u64); + +impl Hasher for IndexTypeIdHasher { + fn write_u32(&mut self, index: u32) { + self.0 ^= u64::from(index); + } + + fn write_u64(&mut self, type_id: u64) { + self.0 ^= type_id; + } + + fn write(&mut self, _bytes: &[u8]) { + unreachable!() + } + + fn finish(&self) -> u64 { + self.0 + } +} + #[cfg(test)] mod tests { use super::*; From bd22bf3279f90a1199bf6394fe3041aedc810edb Mon Sep 17 00:00:00 2001 From: Adam Reichold Date: Sun, 26 Dec 2021 22:22:53 +0100 Subject: [PATCH 3/3] Use a single hash table for compoment insertion edges to improve spatial locality. --- src/world.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/world.rs b/src/world.rs index ab90017c..49f81849 100644 --- a/src/world.rs +++ b/src/world.rs @@ -52,6 +52,9 @@ pub struct World { /// Maps statically-typed bundle types to archetypes bundle_to_archetype: TypeIdMap, /// Maps source archetype and static bundle types to the archetype that an entity is moved to + /// after inserting the components from that bundle. + insert_edges: IndexTypeIdMap, + /// Maps source archetype and static bundle types to the archetype that an entity is moved to /// after removing the components from that bundle. remove_edges: IndexTypeIdMap, id: u64, @@ -73,6 +76,7 @@ impl World { entities: Entities::default(), archetypes: ArchetypeSet::new(), bundle_to_archetype: HashMap::default(), + insert_edges: HashMap::default(), remove_edges: HashMap::default(), id, } @@ -559,15 +563,13 @@ impl World { .get_insert_target(loc.archetype, &components); &target_storage } - Some(key) => match self.archetypes.insert_edges[loc.archetype as usize].get(&key) { + Some(key) => match self.insert_edges.get(&(loc.archetype, key)) { Some(x) => x, None => { let t = self .archetypes .get_insert_target(loc.archetype, &components); - self.archetypes.insert_edges[loc.archetype as usize] - .entry(key) - .or_insert(t) + self.insert_edges.entry((loc.archetype, key)).or_insert(t) } }, }; @@ -1128,10 +1130,6 @@ struct ArchetypeSet { index: HashMap, u32>, archetypes: Vec, generation: u64, - /// Maps static bundle types to the archetype that an entity from this archetype is moved to - /// after inserting the components from that bundle. Stored separately from archetypes to avoid - /// borrowck difficulties in `World::insert`. - insert_edges: Vec>, } impl ArchetypeSet { @@ -1141,7 +1139,6 @@ impl ArchetypeSet { index: Some((Box::default(), 0)).into_iter().collect(), archetypes: vec![Archetype::new(Vec::new())], generation: 0, - insert_edges: vec![HashMap::default()], } } @@ -1198,7 +1195,6 @@ impl ArchetypeSet { } fn post_insert(&mut self) { - self.insert_edges.push(HashMap::default()); self.generation += 1; }