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.


![image](https://github.com/bevyengine/bevy/assets/59848927/f1a5c95d-a882-4dfb-ac07-dd2922273b91)

</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:
Brezak 2024-06-17 16:56:19 +02:00 committed by GitHub
parent 92ac77867d
commit 16e02e1889
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 23 additions and 16 deletions

View File

@ -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

View File

@ -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
};
};

View File

@ -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);
}
}

View File

@ -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);