Skip to content

Commit 87bf0e2

Browse files
committed
Remove unnecessary branching from bundle insertion (#6902)
# Objective Speed up bundle insertion and spawning from a bundle. ## Solution Use the same technique used in #6800 to remove the branch on storage type when writing components from a `Bundle` into storage. - Add a `StorageType` argument to the closure on `Bundle::get_components`. - Pass `C::Storage::STORAGE_TYPE` into that argument. - Match on that argument instead of reading from a `Vec<StorageType>` in `BundleInfo`. - Marked all implementations of `Bundle::get_components` as inline to encourage dead code elimination. The `Vec<StorageType>` in `BundleInfo` was also removed as it's no longer needed. If users were reliant on this, they can either use the compile time constants or fetch the information from `Components`. Should save a rather negligible amount of memory. ## Performance Microbenchmarks show a slight improvement to inserting components into existing entities, as well as spawning from a bundle. Ranging about 8-16% faster depending on the benchmark. ``` group main soft-constant-write-components ----- ---- ------------------------------ add_remove/sparse_set 1.08 1019.0±80.10µs ? ?/sec 1.00 944.6±66.86µs ? ?/sec add_remove/table 1.07 1343.3±20.37µs ? ?/sec 1.00 1257.3±18.13µs ? ?/sec add_remove_big/sparse_set 1.08 1132.4±263.10µs ? ?/sec 1.00 1050.8±240.74µs ? ?/sec add_remove_big/table 1.02 2.6±0.05ms ? ?/sec 1.00 2.5±0.08ms ? ?/sec get_or_spawn/batched 1.15 401.4±17.76µs ? ?/sec 1.00 349.3±11.26µs ? ?/sec get_or_spawn/individual 1.13 732.1±43.35µs ? ?/sec 1.00 645.6±41.44µs ? ?/sec insert_commands/insert 1.12 623.9±37.48µs ? ?/sec 1.00 557.4±34.99µs ? ?/sec insert_commands/insert_batch 1.16 401.4±17.00µs ? ?/sec 1.00 347.4±12.87µs ? ?/sec insert_simple/base 1.08 416.9±5.60µs ? ?/sec 1.00 385.2±4.14µs ? ?/sec insert_simple/unbatched 1.06 934.5±44.58µs ? ?/sec 1.00 881.3±47.86µs ? ?/sec spawn_commands/2000_entities 1.09 190.7±11.41µs ? ?/sec 1.00 174.7±9.15µs ? ?/sec spawn_commands/4000_entities 1.10 386.5±25.33µs ? ?/sec 1.00 352.3±18.81µs ? ?/sec spawn_commands/6000_entities 1.10 586.2±34.42µs ? ?/sec 1.00 535.3±27.25µs ? ?/sec spawn_commands/8000_entities 1.08 778.5±45.15µs ? ?/sec 1.00 718.0±33.66µs ? ?/sec spawn_world/10000_entities 1.04 1026.4±195.46µs ? ?/sec 1.00 985.8±253.37µs ? ?/sec spawn_world/1000_entities 1.06 103.8±20.23µs ? ?/sec 1.00 97.6±18.22µs ? ?/sec spawn_world/100_entities 1.15 11.4±4.25µs ? ?/sec 1.00 9.9±1.87µs ? ?/sec spawn_world/10_entities 1.05 1030.8±229.78ns ? ?/sec 1.00 986.2±231.12ns ? ?/sec spawn_world/1_entities 1.01 105.1±23.33ns ? ?/sec 1.00 104.6±31.84ns ? ?/sec ``` --- ## Changelog Changed: `Bundle::get_components` now takes a `FnMut(StorageType, OwningPtr)`. The provided storage type must be correct for the component being fetched.
1 parent 26d6145 commit 87bf0e2

File tree

2 files changed

+34
-37
lines changed

2 files changed

+34
-37
lines changed

crates/bevy_ecs/macros/src/lib.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,10 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
170170
let struct_name = &ast.ident;
171171

172172
TokenStream::from(quote! {
173-
/// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
173+
// SAFETY:
174+
// - ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
175+
// - `Bundle::get_components` is exactly once for each member. Rely's on the Component -> Bundle implementation to properly pass
176+
// the correct `StorageType` into the callback.
174177
unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name #ty_generics #where_clause {
175178
fn component_ids(
176179
components: &mut #ecs_path::component::Components,
@@ -191,7 +194,11 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
191194
}
192195

193196
#[allow(unused_variables)]
194-
fn get_components(self, func: &mut impl FnMut(#ecs_path::ptr::OwningPtr<'_>)) {
197+
#[inline]
198+
fn get_components(
199+
self,
200+
func: &mut impl FnMut(#ecs_path::component::StorageType, #ecs_path::ptr::OwningPtr<'_>)
201+
) {
195202
#(#field_get_components)*
196203
}
197204
}

crates/bevy_ecs/src/bundle.rs

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use crate::{
99
Archetype, ArchetypeId, ArchetypeRow, Archetypes, BundleComponentStatus, ComponentStatus,
1010
SpawnBundleStatus,
1111
},
12-
component::{Component, ComponentId, Components, StorageType, Tick},
12+
component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick},
1313
entity::{Entities, Entity, EntityLocation},
1414
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
1515
};
@@ -130,7 +130,6 @@ use std::{any::TypeId, collections::HashMap};
130130
/// That is, there is no safe way to implement this trait, and you must not do so.
131131
/// If you want a type to implement [`Bundle`], you must use [`derive@Bundle`](derive@Bundle).
132132
///
133-
///
134133
/// [`Query`]: crate::system::Query
135134
// Some safety points:
136135
// - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the
@@ -159,15 +158,19 @@ pub unsafe trait Bundle: Send + Sync + 'static {
159158
F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>,
160159
Self: Sized;
161160

161+
// SAFETY:
162+
// The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the
163+
// component being fetched.
164+
//
162165
/// Calls `func` on each value, in the order of this bundle's [`Component`]s. This passes
163166
/// ownership of the component values to `func`.
164167
#[doc(hidden)]
165-
fn get_components(self, func: &mut impl FnMut(OwningPtr<'_>));
168+
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>));
166169
}
167170

168171
// SAFETY:
169172
// - `Bundle::component_ids` calls `ids` for C's component id (and nothing else)
170-
// - `Bundle::get_components` is called exactly once for C.
173+
// - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on it's associated constant.
171174
// - `Bundle::from_components` calls `func` exactly once for C, which is the exact value returned by `Bundle::component_ids`.
172175
unsafe impl<C: Component> Bundle for C {
173176
fn component_ids(
@@ -188,8 +191,9 @@ unsafe impl<C: Component> Bundle for C {
188191
func(ctx).read()
189192
}
190193

191-
fn get_components(self, func: &mut impl FnMut(OwningPtr<'_>)) {
192-
OwningPtr::make(self, func);
194+
#[inline]
195+
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
196+
OwningPtr::make(self, |ptr| func(C::Storage::STORAGE_TYPE, ptr));
193197
}
194198
}
195199

@@ -199,6 +203,8 @@ macro_rules! tuple_impl {
199203
// - `Bundle::component_ids` calls `ids` for each component type in the
200204
// bundle, in the exact order that `Bundle::get_components` is called.
201205
// - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`.
206+
// - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct
207+
// `StorageType` into the callback.
202208
unsafe impl<$($name: Bundle),*> Bundle for ($($name,)*) {
203209
#[allow(unused_variables)]
204210
fn component_ids(components: &mut Components, storages: &mut Storages, ids: &mut impl FnMut(ComponentId)){
@@ -217,7 +223,8 @@ macro_rules! tuple_impl {
217223
}
218224

219225
#[allow(unused_variables, unused_mut)]
220-
fn get_components(self, func: &mut impl FnMut(OwningPtr<'_>)) {
226+
#[inline(always)]
227+
fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
221228
#[allow(non_snake_case)]
222229
let ($(mut $name,)*) = self;
223230
$(
@@ -254,7 +261,6 @@ impl SparseSetIndex for BundleId {
254261
pub struct BundleInfo {
255262
pub(crate) id: BundleId,
256263
pub(crate) component_ids: Vec<ComponentId>,
257-
pub(crate) storage_types: Vec<StorageType>,
258264
}
259265

260266
impl BundleInfo {
@@ -268,11 +274,6 @@ impl BundleInfo {
268274
&self.component_ids
269275
}
270276

271-
#[inline]
272-
pub fn storage_types(&self) -> &[StorageType] {
273-
&self.storage_types
274-
}
275-
276277
pub(crate) fn get_bundle_inserter<'a, 'b>(
277278
&'b self,
278279
entities: &'a mut Entities,
@@ -386,9 +387,9 @@ impl BundleInfo {
386387
// NOTE: get_components calls this closure on each component in "bundle order".
387388
// bundle_info.component_ids are also in "bundle order"
388389
let mut bundle_component = 0;
389-
bundle.get_components(&mut |component_ptr| {
390+
bundle.get_components(&mut |storage_type, component_ptr| {
390391
let component_id = *self.component_ids.get_unchecked(bundle_component);
391-
match self.storage_types[bundle_component] {
392+
match storage_type {
392393
StorageType::Table => {
393394
let column = table.get_column_mut(component_id).unwrap();
394395
// SAFETY: bundle_component is a valid index for this bundle
@@ -402,8 +403,11 @@ impl BundleInfo {
402403
}
403404
}
404405
StorageType::SparseSet => {
405-
let sparse_set = sparse_sets.get_mut(component_id).unwrap();
406-
sparse_set.insert(entity, component_ptr, change_tick);
406+
sparse_sets.get_mut(component_id).unwrap().insert(
407+
entity,
408+
component_ptr,
409+
change_tick,
410+
);
407411
}
408412
}
409413
bundle_component += 1;
@@ -707,10 +711,9 @@ impl Bundles {
707711
let mut component_ids = Vec::new();
708712
T::component_ids(components, storages, &mut |id| component_ids.push(id));
709713
let id = BundleId(bundle_infos.len());
710-
// SAFETY: T::component_id ensures info was created
711-
let bundle_info = unsafe {
712-
initialize_bundle(std::any::type_name::<T>(), component_ids, id, components)
713-
};
714+
let bundle_info =
715+
// SAFETY: T::component_id ensures info was created
716+
unsafe { initialize_bundle(std::any::type_name::<T>(), component_ids, id) };
714717
bundle_infos.push(bundle_info);
715718
id
716719
});
@@ -726,16 +729,7 @@ unsafe fn initialize_bundle(
726729
bundle_type_name: &'static str,
727730
component_ids: Vec<ComponentId>,
728731
id: BundleId,
729-
components: &mut Components,
730732
) -> BundleInfo {
731-
let mut storage_types = Vec::new();
732-
733-
for &component_id in &component_ids {
734-
// SAFETY: component_id exists and is therefore valid
735-
let component_info = components.get_info_unchecked(component_id);
736-
storage_types.push(component_info.storage_type());
737-
}
738-
739733
let mut deduped = component_ids.clone();
740734
deduped.sort();
741735
deduped.dedup();
@@ -745,9 +739,5 @@ unsafe fn initialize_bundle(
745739
bundle_type_name
746740
);
747741

748-
BundleInfo {
749-
id,
750-
component_ids,
751-
storage_types,
752-
}
742+
BundleInfo { id, component_ids }
753743
}

0 commit comments

Comments
 (0)