Skip to content

Commit edec09f

Browse files
james7132ItsDoot
authored andcommitted
Replace BlobVec's swap_scratch with a swap_nonoverlapping (bevyengine#4853)
# Objective BlobVec currently relies on a scratch piece of memory allocated at initialization to make a temporary copy of a component when using `swap_remove_and_{forget/drop}`. This is potentially suboptimal as it writes to a, well-known, but random part of memory instead of using the stack. ## Solution As the `FIXME` in the file states, replace `swap_scratch` with a call to `swap_nonoverlapping::<u8>`. The swapped last entry is returned as a `OwnedPtr`. In theory, this should be faster as the temporary swap is allocated on the stack, `swap_nonoverlapping` allows for easier vectorization for bigger types, and the same memory is used between the swap and the returned `OwnedPtr`.
1 parent 6b27419 commit edec09f

File tree

1 file changed

+13
-31
lines changed

1 file changed

+13
-31
lines changed

crates/bevy_ecs/src/storage/blob_vec.rs

+13-31
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@ pub(super) struct BlobVec {
1717
len: usize,
1818
// the `data` ptr's layout is always `array_layout(item_layout, capacity)`
1919
data: NonNull<u8>,
20-
// the `swap_scratch` ptr's layout is always `item_layout`
21-
swap_scratch: NonNull<u8>,
2220
// None if the underlying type doesn't need to be dropped
2321
drop: Option<unsafe fn(OwningPtr<'_>)>,
2422
}
@@ -31,7 +29,6 @@ impl std::fmt::Debug for BlobVec {
3129
.field("capacity", &self.capacity)
3230
.field("len", &self.len)
3331
.field("data", &self.data)
34-
.field("swap_scratch", &self.swap_scratch)
3532
.finish()
3633
}
3734
}
@@ -52,18 +49,14 @@ impl BlobVec {
5249
if item_layout.size() == 0 {
5350
let align = NonZeroUsize::new(item_layout.align()).expect("alignment must be > 0");
5451
BlobVec {
55-
swap_scratch: NonNull::dangling(),
5652
data: bevy_ptr::dangling_with_align(align),
5753
capacity: usize::MAX,
5854
len: 0,
5955
item_layout,
6056
drop,
6157
}
6258
} else {
63-
let swap_scratch = NonNull::new(std::alloc::alloc(item_layout))
64-
.unwrap_or_else(|| std::alloc::handle_alloc_error(item_layout));
6559
let mut blob_vec = BlobVec {
66-
swap_scratch,
6760
data: NonNull::dangling(),
6861
capacity: 0,
6962
len: 0,
@@ -213,28 +206,23 @@ impl BlobVec {
213206
/// caller's responsibility to drop the returned pointer, if that is desirable.
214207
///
215208
/// # Safety
216-
/// It is the caller's responsibility to ensure that `index` is < `self.len()`
209+
/// It is the caller's responsibility to ensure that `index` is less than `self.len()`.
217210
#[inline]
218211
#[must_use = "The returned pointer should be used to dropped the removed element"]
219212
pub unsafe fn swap_remove_and_forget_unchecked(&mut self, index: usize) -> OwningPtr<'_> {
220-
// FIXME: This should probably just use `core::ptr::swap` and return an `OwningPtr`
221-
// into the underlying `BlobVec` allocation, and remove swap_scratch
222-
223213
debug_assert!(index < self.len());
224-
let last = self.len - 1;
225-
let swap_scratch = self.swap_scratch.as_ptr();
226-
std::ptr::copy_nonoverlapping::<u8>(
227-
self.get_unchecked_mut(index).as_ptr(),
228-
swap_scratch,
229-
self.item_layout.size(),
230-
);
231-
std::ptr::copy::<u8>(
232-
self.get_unchecked_mut(last).as_ptr(),
233-
self.get_unchecked_mut(index).as_ptr(),
234-
self.item_layout.size(),
235-
);
236-
self.len -= 1;
237-
OwningPtr::new(self.swap_scratch)
214+
let new_len = self.len - 1;
215+
let size = self.item_layout.size();
216+
if index != new_len {
217+
std::ptr::swap_nonoverlapping::<u8>(
218+
self.get_unchecked_mut(index).as_ptr(),
219+
self.get_unchecked_mut(new_len).as_ptr(),
220+
size,
221+
);
222+
}
223+
self.len = new_len;
224+
// Cannot use get_unchecked here as this is technically out of bounds after changing len.
225+
self.get_ptr_mut().byte_add(new_len * size).promote()
238226
}
239227

240228
/// Removes the value at `index` and copies the value stored into `ptr`.
@@ -333,12 +321,6 @@ impl BlobVec {
333321
impl Drop for BlobVec {
334322
fn drop(&mut self) {
335323
self.clear();
336-
if self.item_layout.size() > 0 {
337-
// SAFETY: the `swap_scratch` pointer is always allocated using `self.item_layout`
338-
unsafe {
339-
std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout);
340-
}
341-
}
342324
let array_layout =
343325
array_layout(&self.item_layout, self.capacity).expect("array layout should be valid");
344326
if array_layout.size() > 0 {

0 commit comments

Comments
 (0)