Skip to content

Commit b1b3bd5

Browse files
committed
Skip drop when needs_drop is false (#4773)
# Objective - We do a lot of function pointer calls in a hot loop (clearing entities in render). This is slow, since calling function pointers cannot be optimised out. We can avoid that in the cases where the function call is a no-op. - Alternative to #2897 - On my machine, in `many_cubes`, this reduces dropping time from ~150μs to ~80μs. ## Solution - Make `drop` in `BlobVec` an `Option`, recording whether the given drop impl is required or not. - Note that this does add branching in some cases - we could consider splitting this into two fields, i.e. unconditionally call the `drop` fn pointer. - My intuition of how often types stored in `World` should have non-trivial drops makes me think that would be slower, however. N.B. Even once this lands, we should still test having a 'drop_multiple' variant - for types with a real `Drop` impl, the current implementation is definitely optimal.
1 parent 85dd291 commit b1b3bd5

File tree

3 files changed

+44
-21
lines changed

3 files changed

+44
-21
lines changed

crates/bevy_ecs/src/component.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use bevy_ptr::OwningPtr;
1010
use std::{
1111
alloc::Layout,
1212
any::{Any, TypeId},
13+
mem::needs_drop,
1314
};
1415

1516
/// A component is data associated with an [`Entity`](crate::entity::Entity). Each entity can have
@@ -111,7 +112,13 @@ impl ComponentInfo {
111112
}
112113

113114
#[inline]
114-
pub fn drop(&self) -> unsafe fn(OwningPtr<'_>) {
115+
/// Get the function which should be called to clean up values of
116+
/// the underlying component type. This maps to the
117+
/// [`Drop`] implementation for 'normal' Rust components
118+
///
119+
/// Returns `None` if values of the underlying component type don't
120+
/// need to be dropped, e.g. as reported by [`needs_drop`].
121+
pub fn drop(&self) -> Option<unsafe fn(OwningPtr<'_>)> {
115122
self.descriptor.drop
116123
}
117124

@@ -168,7 +175,8 @@ pub struct ComponentDescriptor {
168175
layout: Layout,
169176
// SAFETY: this function must be safe to call with pointers pointing to items of the type
170177
// this descriptor describes.
171-
drop: for<'a> unsafe fn(OwningPtr<'a>),
178+
// None if the underlying type doesn't need to be dropped
179+
drop: Option<for<'a> unsafe fn(OwningPtr<'a>)>,
172180
}
173181

174182
// We need to ignore the `drop` field in our `Debug` impl
@@ -197,7 +205,7 @@ impl ComponentDescriptor {
197205
is_send_and_sync: true,
198206
type_id: Some(TypeId::of::<T>()),
199207
layout: Layout::new::<T>(),
200-
drop: Self::drop_ptr::<T>,
208+
drop: needs_drop::<T>().then(|| Self::drop_ptr::<T> as _),
201209
}
202210
}
203211

@@ -213,7 +221,7 @@ impl ComponentDescriptor {
213221
is_send_and_sync: true,
214222
type_id: Some(TypeId::of::<T>()),
215223
layout: Layout::new::<T>(),
216-
drop: Self::drop_ptr::<T>,
224+
drop: needs_drop::<T>().then(|| Self::drop_ptr::<T> as _),
217225
}
218226
}
219227

@@ -224,7 +232,7 @@ impl ComponentDescriptor {
224232
is_send_and_sync: false,
225233
type_id: Some(TypeId::of::<T>()),
226234
layout: Layout::new::<T>(),
227-
drop: Self::drop_ptr::<T>,
235+
drop: needs_drop::<T>().then(|| Self::drop_ptr::<T> as _),
228236
}
229237
}
230238

crates/bevy_ecs/src/storage/blob_vec.rs

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ pub struct BlobVec {
1616
len: usize,
1717
data: NonNull<u8>,
1818
swap_scratch: NonNull<u8>,
19-
drop: unsafe fn(OwningPtr<'_>),
19+
// None if the underlying type doesn't need to be dropped
20+
drop: Option<unsafe fn(OwningPtr<'_>)>,
2021
}
2122

2223
// We want to ignore the `drop` field in our `Debug` impl
@@ -36,9 +37,13 @@ impl BlobVec {
3637
/// # Safety
3738
///
3839
/// `drop` should be safe to call with an [`OwningPtr`] pointing to any item that's been pushed into this [`BlobVec`].
40+
///
41+
/// If `drop` is `None`, the items will be leaked. This should generally be set as None based on [`needs_drop`].
42+
///
43+
/// [`needs_drop`]: core::mem::needs_drop
3944
pub unsafe fn new(
4045
item_layout: Layout,
41-
drop: unsafe fn(OwningPtr<'_>),
46+
drop: Option<unsafe fn(OwningPtr<'_>)>,
4247
capacity: usize,
4348
) -> BlobVec {
4449
if item_layout.size() == 0 {
@@ -141,7 +146,9 @@ impl BlobVec {
141146
let ptr = self.get_unchecked_mut(index).promote().as_ptr();
142147
self.len = 0;
143148
// Drop the old value, then write back, justifying the promotion
144-
(self.drop)(OwningPtr::new(NonNull::new_unchecked(ptr)));
149+
if let Some(drop) = self.drop {
150+
(drop)(OwningPtr::new(NonNull::new_unchecked(ptr)));
151+
}
145152
std::ptr::copy_nonoverlapping::<u8>(value.as_ptr(), ptr, self.item_layout.size());
146153
self.len = old_len;
147154
}
@@ -204,7 +211,9 @@ impl BlobVec {
204211
debug_assert!(index < self.len());
205212
let drop = self.drop;
206213
let value = self.swap_remove_and_forget_unchecked(index);
207-
(drop)(value);
214+
if let Some(drop) = drop {
215+
(drop)(value);
216+
}
208217
}
209218

210219
/// # Safety
@@ -252,14 +261,15 @@ impl BlobVec {
252261
// We set len to 0 _before_ dropping elements for unwind safety. This ensures we don't
253262
// accidentally drop elements twice in the event of a drop impl panicking.
254263
self.len = 0;
255-
let drop = self.drop;
256-
let layout_size = self.item_layout.size();
257-
for i in 0..len {
258-
unsafe {
259-
// NOTE: this doesn't use self.get_unchecked(i) because the debug_assert on index
260-
// will panic here due to self.len being set to 0
261-
let ptr = self.get_ptr_mut().byte_add(i * layout_size).promote();
262-
(drop)(ptr);
264+
if let Some(drop) = self.drop {
265+
let layout_size = self.item_layout.size();
266+
for i in 0..len {
267+
unsafe {
268+
// NOTE: this doesn't use self.get_unchecked(i) because the debug_assert on index
269+
// will panic here due to self.len being set to 0
270+
let ptr = self.get_ptr_mut().byte_add(i * layout_size).promote();
271+
(drop)(ptr);
272+
}
263273
}
264274
}
265275
}
@@ -375,8 +385,8 @@ mod tests {
375385
#[test]
376386
fn resize_test() {
377387
let item_layout = Layout::new::<usize>();
378-
let drop = drop_ptr::<usize>;
379-
let mut blob_vec = unsafe { BlobVec::new(item_layout, drop, 64) };
388+
// usize doesn't need dropping
389+
let mut blob_vec = unsafe { BlobVec::new(item_layout, None, 64) };
380390
unsafe {
381391
for i in 0..1_000 {
382392
push(&mut blob_vec, i as usize);
@@ -406,7 +416,7 @@ mod tests {
406416
{
407417
let item_layout = Layout::new::<Foo>();
408418
let drop = drop_ptr::<Foo>;
409-
let mut blob_vec = unsafe { BlobVec::new(item_layout, drop, 2) };
419+
let mut blob_vec = unsafe { BlobVec::new(item_layout, Some(drop), 2) };
410420
assert_eq!(blob_vec.capacity(), 2);
411421
unsafe {
412422
let foo1 = Foo {
@@ -466,6 +476,6 @@ mod tests {
466476
fn blob_vec_drop_empty_capacity() {
467477
let item_layout = Layout::new::<Foo>();
468478
let drop = drop_ptr::<Foo>;
469-
let _ = unsafe { BlobVec::new(item_layout, drop, 0) };
479+
let _ = unsafe { BlobVec::new(item_layout, Some(drop), 0) };
470480
}
471481
}

crates/bevy_render/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,11 @@ impl Plugin for RenderPlugin {
273273
.get_stage_mut::<SystemStage>(&RenderStage::Cleanup)
274274
.unwrap();
275275
cleanup.run(&mut render_app.world);
276+
}
277+
{
278+
#[cfg(feature = "trace")]
279+
let _stage_span =
280+
bevy_utils::tracing::info_span!("stage", name = "clear_entities").entered();
276281

277282
render_app.world.clear_entities();
278283
}

0 commit comments

Comments
 (0)