Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove redundent information and optimize dynamic allocations in Table #12929

Open
wants to merge 64 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
5564de5
added blob_array
Adamkob12 Apr 5, 2024
7d74489
seperated column into different file
Adamkob12 Apr 5, 2024
f5baf43
implemented ThinArrayPtr, not tested yet
Adamkob12 Apr 5, 2024
8ddf250
defined new Column type, working on adjusting Table
Adamkob12 Apr 6, 2024
ec4fb50
added almost all the methods needed for ColumnWIP, need refactor now …
Adamkob12 Apr 6, 2024
c2aed7d
refactor + cleaning up methods and adding a couple more to ColumnWIP
Adamkob12 Apr 6, 2024
e4bb500
renamed ColumnWIP to Column and deleted previous Column
Adamkob12 Apr 6, 2024
2e7c06c
Revert "renamed ColumnWIP to Column and deleted previous Column"
Adamkob12 Apr 6, 2024
eb51eb6
no errors
Adamkob12 Apr 8, 2024
bb77e01
fixing bugs, passed all tests expcept one
Adamkob12 Apr 9, 2024
572a289
Table::alloc_columns
Adamkob12 Apr 9, 2024
d0b5b9d
fix some warnings
Adamkob12 Apr 9, 2024
dbd477a
checkpoint
Adamkob12 Apr 10, 2024
4809fe0
trying to solve abort (because of double free?) at test `panic_while_…
Adamkob12 Apr 10, 2024
3d09567
fixed double drop in unwind
Adamkob12 Apr 10, 2024
b522b3a
all tests passed
Adamkob12 Apr 10, 2024
2c76208
chipping away at TODOs
Adamkob12 Apr 10, 2024
20285b4
docs
Adamkob12 Apr 11, 2024
3aaebf2
doc nits
Adamkob12 Apr 11, 2024
a20de1a
added benchmark for adding removing a (very) lot of components per en…
Adamkob12 Apr 11, 2024
9c6d229
remove duplicate test
Adamkob12 Apr 11, 2024
6782cc7
clippy
Adamkob12 Apr 11, 2024
1c52357
typos
Adamkob12 Apr 11, 2024
d6467c1
doc tests
Adamkob12 Apr 11, 2024
ddaf16f
ci
Adamkob12 Apr 11, 2024
568cc42
ci
Adamkob12 Apr 11, 2024
9d55340
fixed double free
Adamkob12 Apr 12, 2024
5ef9e54
fix some ci (i think the other is bugged)
Adamkob12 Apr 12, 2024
fb794cc
expand thinarrayptr in debug mode
Adamkob12 Apr 12, 2024
3153f28
docs
Adamkob12 Apr 12, 2024
1c1690e
Merge branch 'main' into remove_lens_and_caps_from_table
Adamkob12 Apr 13, 2024
de25be0
x
Adamkob12 Apr 19, 2024
5732351
Update crates/bevy_ecs/src/storage/thin_array_ptr.rs
Adamkob12 Apr 19, 2024
1d44fdd
x
Adamkob12 Apr 19, 2024
faec660
x
Adamkob12 Apr 19, 2024
0ecff6b
expand add_remove_very_big benchmark
Adamkob12 Apr 19, 2024
8004d80
added ZSTs to add_remove_very_big benchmark
Adamkob12 Apr 19, 2024
ce583c6
checking why test failed
Adamkob12 Apr 19, 2024
8ec9eca
fixed test fail
Adamkob12 Apr 19, 2024
ac9d9f2
docs
Adamkob12 Apr 19, 2024
976c553
merge main into branch
Adamkob12 Apr 19, 2024
2a21b6b
typo
Adamkob12 Apr 19, 2024
abaa9a0
docs
Adamkob12 Apr 19, 2024
96b3e79
Merge branch 'main' into remove_lens_and_caps_from_table
Adamkob12 Apr 19, 2024
7f62022
Update crates/bevy_ecs/src/storage/thin_array_ptr.rs
Adamkob12 Apr 19, 2024
3a13021
added inline
Adamkob12 Apr 19, 2024
96a3602
abort on allocation panic
Adamkob12 Apr 19, 2024
49d1807
x
Adamkob12 Apr 19, 2024
fc8f66f
x
Adamkob12 Apr 19, 2024
e150dca
renamed some methods, fixed some docs
Adamkob12 Apr 21, 2024
451198d
added back some public methods that were removed from Column
Adamkob12 Apr 21, 2024
b5f6ddb
solve merge conflicts
Adamkob12 Apr 21, 2024
cba8158
ci
Adamkob12 Apr 21, 2024
81b47fe
fix edgecase where components wouldn't be dropped when removed
Adamkob12 Apr 21, 2024
4f35607
docs nits
Adamkob12 Apr 21, 2024
494b85a
pub -> pub(crate) in some methods in Table and ThinColumn | docs
Adamkob12 Apr 24, 2024
d44fe5b
Fix ZST components not being dropped, test to make sure
Adamkob12 May 13, 2024
22911a6
docs
Adamkob12 May 13, 2024
fbf6abf
Merge branch 'bevyengine:main' into remove_lens_and_caps_from_table
Adamkob12 May 13, 2024
457b8cc
LMerge remote-tracking branch 'refs/remotes/origin/remove_lens_and_ca…
Adamkob12 May 13, 2024
a109aef
clippy
Adamkob12 May 13, 2024
99826e6
Merge branch 'main' into remove_lens_and_caps_from_table
Adamkob12 May 13, 2024
ed8195b
x
Adamkob12 May 14, 2024
3845074
Merge remote-tracking branch 'refs/remotes/origin/remove_lens_and_cap…
Adamkob12 May 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
59 changes: 54 additions & 5 deletions crates/bevy_ecs/src/storage/blob_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,17 +333,41 @@ impl BlobArray {
index_to_keep: usize,
) -> OwningPtr<'_> {
if index_to_remove != index_to_keep {
std::ptr::swap_nonoverlapping::<u8>(
self.get_unchecked_mut(index_to_keep).as_ptr(),
self.get_unchecked_mut(index_to_remove).as_ptr(),
self.item_layout.size(),
);
return self
.swap_remove_and_forget_unchecked_nonoverlapping(index_to_remove, index_to_keep);
}
// Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
// If we are storing ZSTs than the index doesn't actually matter because the size is 0.
self.get_unchecked_mut(index_to_keep).promote()
}

/// The same as [`Self::swap_remove_and_forget_unchecked`] but the two elements must non-overlapping.
///
/// # Safety
/// The caller must ensure that:
/// - `index_to_keep` < `len`
/// - `index_to_remove` < `len`
/// - `index_to_remove` != `index_to_keep`
/// - If `index_to_keep` == `len` - 1, and the caller has the length saved, update the length to reflect that the element with index
/// `len` - 1 is not valid to use (set `len` to `len` - 1).
/// - If the length wasn't updated by the caller, they must use [`Self::initialize_unchecked`] to initialize an element in the index `index_to_keep`,
/// because after calling this method, the element with index `index_to_keep` will not be valid to use.
#[inline]
pub unsafe fn swap_remove_and_forget_unchecked_nonoverlapping(
&mut self,
index_to_remove: usize,
index_to_keep: usize,
) -> OwningPtr<'_> {
std::ptr::swap_nonoverlapping::<u8>(
self.get_unchecked_mut(index_to_keep).as_ptr(),
self.get_unchecked_mut(index_to_remove).as_ptr(),
self.item_layout.size(),
);
// Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
// If we are storing ZSTs than the index doesn't actually matter because the size is 0.
self.get_unchecked_mut(index_to_keep).promote()
}

/// This method will swap two elements in the array, and drop the one with the index `index_to_remove`.
///
/// *`len` refers to the length of the array, the number of elements that have been initialized, and are safe to read.
Expand Down Expand Up @@ -373,4 +397,29 @@ impl BlobArray {
drop(value);
}
}

/// The same as [`Self::swap_remove_and_drop_unchecked`] but the two elements must non-overlapping.
///
/// # Safety
/// The caller must ensure that:
/// - `index_to_keep` < `len`
/// - `index_to_remove` < `len`
/// - `index_to_remove` != `index_to_keep`
/// - If `index_to_keep` == `len` - 1, and the caller has the length saved, update the length to reflect that the element with index
/// `len` - 1 is not valid to use (set `len` to `len` - 1).
/// - If the length wasn't updated by the caller, they must use [`Self::initialize_unchecked`] to initialize an element in the index `index_to_keep`,
/// because after calling this method, the element with index `index_to_keep` will not be valid to use.
#[inline]
pub unsafe fn swap_remove_and_drop_unchecked_nonoverlapping(
&mut self,
index_to_remove: usize,
index_to_keep: usize,
) {
let drop = self.drop;
let value =
self.swap_remove_and_forget_unchecked_nonoverlapping(index_to_remove, index_to_keep);
if let Some(drop) = drop {
drop(value);
}
}
}
24 changes: 24 additions & 0 deletions crates/bevy_ecs/src/storage/table/column.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,30 @@ impl ThinColumn {
}
}

/// Swap-remove and drop the removed element, but the component at `row` must not be the last elment.
///
/// # Safety
/// - `row.as_usize()` < `len`
/// - `last_element_index` = `len - 1`
/// - `last_element_index` != `row.as_usize()`
/// - _update the `len`_ to `len - 1`, or immediately initialize another element in the `last_element_index`
pub unsafe fn swap_remove_and_drop_unchecked_nonoverlapping(
&mut self,
last_element_index: usize,
row: TableRow,
) {
self.data
.swap_remove_and_drop_unchecked_nonoverlapping(row.as_usize(), last_element_index);
let added = &mut self
.added_ticks
.swap_remove_and_forget_unchecked_nonoverlapping(row.as_usize(), last_element_index);
let changed = &mut self
.changed_ticks
.swap_remove_and_forget_unchecked_nonoverlapping(row.as_usize(), last_element_index);
std::ptr::drop_in_place(added as *mut UnsafeCell<Tick>);
Adamkob12 marked this conversation as resolved.
Show resolved Hide resolved
std::ptr::drop_in_place(changed as *mut UnsafeCell<Tick>);
}

/// Swap-remove and drop the removed element.
///
/// # Safety
Expand Down
64 changes: 36 additions & 28 deletions crates/bevy_ecs/src/storage/table/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ pub(crate) struct TableBuilder {
}

impl TableBuilder {
/// Start building a new [`Table`] with a specified `column_capacity` (How many components per column?) and a `capacity` (How many columns?)
/// Start building a new [`Table`] with a specified `column_capacity` (How many components per column?) and a `capacity` (How many columns?)
pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self {
Self {
columns: SparseSet::with_capacity(column_capacity),
Expand All @@ -175,7 +175,6 @@ impl TableBuilder {
Table {
columns: self.columns.into_immutable(),
entities: Vec::with_capacity(self.capacity),
poisoned: false,
}
}
}
Expand All @@ -195,8 +194,15 @@ impl TableBuilder {
pub struct Table {
columns: ImmutableSparseSet<ComponentId, ThinColumn>,
entities: Vec<Entity>,
/// This is set to `true` if an allocation triggered an unwind
poisoned: bool,
}

struct AbortOnPanic;

impl Drop for AbortOnPanic {
fn drop(&mut self) {
// Panicking while unwinding will force an abort.
panic!("Aborting due to allocator error");
}
}

impl Table {
Expand Down Expand Up @@ -227,12 +233,17 @@ impl Table {
pub(crate) unsafe fn swap_remove_unchecked(&mut self, row: TableRow) -> Option<Entity> {
debug_assert!(row.as_usize() < self.len());
let last_element_index = self.len() - 1;
for col in self.columns.values_mut() {
// SAFETY:
// - `row` < `len`
// - `last_element_index` = `len` - 1
// - the `len` is kept within `self.entities`, it will update accordingly.
unsafe { col.swap_remove_and_drop_unchecked(last_element_index, row) };
if row.as_usize() != last_element_index {
for col in self.columns.values_mut() {
// SAFETY:
// - `row` < `len`
// - `last_element_index` = `len` - 1
// - `row` != last_elment_index
// - the `len` is kept within `self.entities`, it will update accordingly.
unsafe {
col.swap_remove_and_drop_unchecked_nonoverlapping(last_element_index, row)
};
}
}
let is_last = row.as_usize() == last_element_index;
self.entities.swap_remove(row.as_usize());
Expand Down Expand Up @@ -268,7 +279,6 @@ impl Table {
column.swap_remove_and_forget_unchecked(last_element_index, row);
}
}

TableMoveResult {
new_row,
swapped_entity: if is_last {
Expand Down Expand Up @@ -529,14 +539,14 @@ impl Table {
///
/// The current capacity of the columns should be 0, if it's not 0, then the previous data will be overwritten and leaked.
fn alloc_columns(&mut self, new_capacity: NonZeroUsize) {
self.poisoned = true;
// If any of these allocations trigger an unwind, the wrong capacity will be used while dropping this table - UB.
// To avoid this, we manually set `self.poisoned` to true. If the drop method is called when `self.poisoned` is true,
// nothing will be deallocated - avoiding possible UB (but still leaking memory).
// To avoid this, we use `AbortOnPanic`. If the allocation triggered a panic, the `AbortOnPanic`'s Drop impl will be
// called, and abort the program.
let _guard = AbortOnPanic;
for col in self.columns.values_mut() {
col.alloc(new_capacity);
}
self.poisoned = false; // The allocations didn't trigger an unwind, so we set the flag to `false`.
core::mem::forget(_guard); // The allocation was successful, so we don't drop the guard.
}

/// Reallocate memory for the columns in the [`Table`]
Expand All @@ -548,10 +558,10 @@ impl Table {
current_column_capacity: NonZeroUsize,
new_capacity: NonZeroUsize,
) {
self.poisoned = true;
// If any of these allocations trigger an unwind, the wrong capacity will be used while dropping this table - UB.
// To avoid this, we manually set `self.poisoned` to true. If the drop method is called when `self.poisoned` is true,
// nothing will be deallocated - avoiding possible UB (but still leaking memory).
// To avoid this, we use `AbortOnPanic`. If the allocation triggered a panic, the `AbortOnPanic`'s Drop impl will be
// called, and abort the program.
let _guard = AbortOnPanic;

// SAFETY:
// - There's no overflow
Expand All @@ -560,7 +570,7 @@ impl Table {
for col in self.columns.values_mut() {
col.realloc(current_column_capacity, new_capacity);
}
self.poisoned = false; // The allocations didn't trigger an unwind, so we set the flag to `false`.
core::mem::forget(_guard); // The allocation was successful, so we don't drop the guard.
}

/// Allocates space for a new entity
Expand Down Expand Up @@ -791,15 +801,13 @@ impl IndexMut<TableId> for Tables {

impl Drop for Table {
fn drop(&mut self) {
if !self.poisoned {
let len = self.len();
let cap = self.capacity();
self.entities.clear();
for col in self.columns.values_mut() {
// SAFETY: `cap` and `len` are correct
unsafe {
col.drop(cap, len);
}
let len = self.len();
let cap = self.capacity();
self.entities.clear();
for col in self.columns.values_mut() {
// SAFETY: `cap` and `len` are correct
unsafe {
col.drop(cap, len);
}
}
}
Expand Down