Use a unstable sort to sort component ids in bevy_ecs
(#13789)
# Objective
While writing code for the `bevy_ecs` I noticed we were using a
unnecessarily stable sort to sort component ids
## Solution
- Sort component ids with a unstable sort
- Comb the bevy_ecs crate for any other obvious inefficiencies.
- Don't clone component vectors when inserting an archetype.
## Testing
I ran `cargo test -p bevy_ecs`. Everything else I leave to CI.
## Profiling
I measured about a 1% speed increase when spawning entities directly
into a world. Since the difference is so small (and might just be noise)
I didn't bother to figure out which of change if any made the biggest
difference.
<details>
<summary> Tracy data </summary>
Yellow is this PR. Red is the commit I branched from.

</details>
<details>
<summary>Methodology</summary>
I created a system that spawn a 1000 entities each with the same 30
components each frame, and then I measured it's run time. The unusually
high number of components was chosen because the standard library [will
use a insertion sort for slices under 20
elements](0de24a5177/library/core/src/slice/sort.rs (L1048-L1049)
).
This holds for both stable and unstable sorts.
</details>
This commit is contained in:
parent
92ac77867d
commit
16e02e1889
@ -822,8 +822,8 @@ impl Archetypes {
|
||||
sparse_set_components: Vec<ComponentId>,
|
||||
) -> ArchetypeId {
|
||||
let archetype_identity = ArchetypeComponents {
|
||||
sparse_set_components: sparse_set_components.clone().into_boxed_slice(),
|
||||
table_components: table_components.clone().into_boxed_slice(),
|
||||
sparse_set_components: sparse_set_components.into_boxed_slice(),
|
||||
table_components: table_components.into_boxed_slice(),
|
||||
};
|
||||
|
||||
let archetypes = &mut self.archetypes;
|
||||
@ -831,24 +831,35 @@ impl Archetypes {
|
||||
*self
|
||||
.by_components
|
||||
.entry(archetype_identity)
|
||||
.or_insert_with(move || {
|
||||
.or_insert_with_key(move |identity| {
|
||||
let ArchetypeComponents {
|
||||
table_components,
|
||||
sparse_set_components,
|
||||
} = identity;
|
||||
|
||||
let id = ArchetypeId::new(archetypes.len());
|
||||
let table_start = *archetype_component_count;
|
||||
*archetype_component_count += table_components.len();
|
||||
let table_archetype_components =
|
||||
(table_start..*archetype_component_count).map(ArchetypeComponentId);
|
||||
|
||||
let sparse_start = *archetype_component_count;
|
||||
*archetype_component_count += sparse_set_components.len();
|
||||
let sparse_set_archetype_components =
|
||||
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
|
||||
|
||||
archetypes.push(Archetype::new(
|
||||
components,
|
||||
observers,
|
||||
id,
|
||||
table_id,
|
||||
table_components.into_iter().zip(table_archetype_components),
|
||||
table_components
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(table_archetype_components),
|
||||
sparse_set_components
|
||||
.into_iter()
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(sparse_set_archetype_components),
|
||||
));
|
||||
id
|
||||
|
@ -324,7 +324,7 @@ impl BundleInfo {
|
||||
id: BundleId,
|
||||
) -> BundleInfo {
|
||||
let mut deduped = component_ids.clone();
|
||||
deduped.sort();
|
||||
deduped.sort_unstable();
|
||||
deduped.dedup();
|
||||
|
||||
if deduped.len() != component_ids.len() {
|
||||
@ -492,7 +492,7 @@ impl BundleInfo {
|
||||
} else {
|
||||
new_table_components.extend(current_archetype.table_components());
|
||||
// sort to ignore order while hashing
|
||||
new_table_components.sort();
|
||||
new_table_components.sort_unstable();
|
||||
// SAFETY: all component ids in `new_table_components` exist
|
||||
table_id = unsafe {
|
||||
storages
|
||||
@ -508,7 +508,7 @@ impl BundleInfo {
|
||||
} else {
|
||||
new_sparse_set_components.extend(current_archetype.sparse_set_components());
|
||||
// sort to ignore order while hashing
|
||||
new_sparse_set_components.sort();
|
||||
new_sparse_set_components.sort_unstable();
|
||||
new_sparse_set_components
|
||||
};
|
||||
};
|
||||
|
@ -320,8 +320,7 @@ where
|
||||
// unblock this node's ancestors
|
||||
while let Some(n) = unblock_stack.pop() {
|
||||
if blocked.remove(&n) {
|
||||
let unblock_predecessors =
|
||||
unblock_together.entry(n).or_insert_with(HashSet::new);
|
||||
let unblock_predecessors = unblock_together.entry(n).or_default();
|
||||
unblock_stack.extend(unblock_predecessors.iter());
|
||||
unblock_predecessors.clear();
|
||||
}
|
||||
@ -329,10 +328,7 @@ where
|
||||
} else {
|
||||
// if its descendants can be unblocked later, this node will be too
|
||||
for successor in subgraph.neighbors(*node) {
|
||||
unblock_together
|
||||
.entry(successor)
|
||||
.or_insert_with(HashSet::new)
|
||||
.insert(*node);
|
||||
unblock_together.entry(successor).or_default().insert(*node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2375,8 +2375,8 @@ unsafe fn remove_bundle_from_archetype(
|
||||
|
||||
// sort removed components so we can do an efficient "sorted remove". archetype
|
||||
// components are already sorted
|
||||
removed_table_components.sort();
|
||||
removed_sparse_set_components.sort();
|
||||
removed_table_components.sort_unstable();
|
||||
removed_sparse_set_components.sort_unstable();
|
||||
next_table_components = current_archetype.table_components().collect();
|
||||
next_sparse_set_components = current_archetype.sparse_set_components().collect();
|
||||
sorted_remove(&mut next_table_components, &removed_table_components);
|
||||
|
Loading…
Reference in New Issue
Block a user