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>,
|
sparse_set_components: Vec<ComponentId>,
|
||||||
) -> ArchetypeId {
|
) -> ArchetypeId {
|
||||||
let archetype_identity = ArchetypeComponents {
|
let archetype_identity = ArchetypeComponents {
|
||||||
sparse_set_components: sparse_set_components.clone().into_boxed_slice(),
|
sparse_set_components: sparse_set_components.into_boxed_slice(),
|
||||||
table_components: table_components.clone().into_boxed_slice(),
|
table_components: table_components.into_boxed_slice(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let archetypes = &mut self.archetypes;
|
let archetypes = &mut self.archetypes;
|
||||||
@ -831,24 +831,35 @@ impl Archetypes {
|
|||||||
*self
|
*self
|
||||||
.by_components
|
.by_components
|
||||||
.entry(archetype_identity)
|
.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 id = ArchetypeId::new(archetypes.len());
|
||||||
let table_start = *archetype_component_count;
|
let table_start = *archetype_component_count;
|
||||||
*archetype_component_count += table_components.len();
|
*archetype_component_count += table_components.len();
|
||||||
let table_archetype_components =
|
let table_archetype_components =
|
||||||
(table_start..*archetype_component_count).map(ArchetypeComponentId);
|
(table_start..*archetype_component_count).map(ArchetypeComponentId);
|
||||||
|
|
||||||
let sparse_start = *archetype_component_count;
|
let sparse_start = *archetype_component_count;
|
||||||
*archetype_component_count += sparse_set_components.len();
|
*archetype_component_count += sparse_set_components.len();
|
||||||
let sparse_set_archetype_components =
|
let sparse_set_archetype_components =
|
||||||
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
|
(sparse_start..*archetype_component_count).map(ArchetypeComponentId);
|
||||||
|
|
||||||
archetypes.push(Archetype::new(
|
archetypes.push(Archetype::new(
|
||||||
components,
|
components,
|
||||||
observers,
|
observers,
|
||||||
id,
|
id,
|
||||||
table_id,
|
table_id,
|
||||||
table_components.into_iter().zip(table_archetype_components),
|
table_components
|
||||||
|
.iter()
|
||||||
|
.copied()
|
||||||
|
.zip(table_archetype_components),
|
||||||
sparse_set_components
|
sparse_set_components
|
||||||
.into_iter()
|
.iter()
|
||||||
|
.copied()
|
||||||
.zip(sparse_set_archetype_components),
|
.zip(sparse_set_archetype_components),
|
||||||
));
|
));
|
||||||
id
|
id
|
||||||
|
@ -324,7 +324,7 @@ impl BundleInfo {
|
|||||||
id: BundleId,
|
id: BundleId,
|
||||||
) -> BundleInfo {
|
) -> BundleInfo {
|
||||||
let mut deduped = component_ids.clone();
|
let mut deduped = component_ids.clone();
|
||||||
deduped.sort();
|
deduped.sort_unstable();
|
||||||
deduped.dedup();
|
deduped.dedup();
|
||||||
|
|
||||||
if deduped.len() != component_ids.len() {
|
if deduped.len() != component_ids.len() {
|
||||||
@ -492,7 +492,7 @@ impl BundleInfo {
|
|||||||
} else {
|
} else {
|
||||||
new_table_components.extend(current_archetype.table_components());
|
new_table_components.extend(current_archetype.table_components());
|
||||||
// sort to ignore order while hashing
|
// sort to ignore order while hashing
|
||||||
new_table_components.sort();
|
new_table_components.sort_unstable();
|
||||||
// SAFETY: all component ids in `new_table_components` exist
|
// SAFETY: all component ids in `new_table_components` exist
|
||||||
table_id = unsafe {
|
table_id = unsafe {
|
||||||
storages
|
storages
|
||||||
@ -508,7 +508,7 @@ impl BundleInfo {
|
|||||||
} else {
|
} else {
|
||||||
new_sparse_set_components.extend(current_archetype.sparse_set_components());
|
new_sparse_set_components.extend(current_archetype.sparse_set_components());
|
||||||
// sort to ignore order while hashing
|
// sort to ignore order while hashing
|
||||||
new_sparse_set_components.sort();
|
new_sparse_set_components.sort_unstable();
|
||||||
new_sparse_set_components
|
new_sparse_set_components
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -320,8 +320,7 @@ where
|
|||||||
// unblock this node's ancestors
|
// unblock this node's ancestors
|
||||||
while let Some(n) = unblock_stack.pop() {
|
while let Some(n) = unblock_stack.pop() {
|
||||||
if blocked.remove(&n) {
|
if blocked.remove(&n) {
|
||||||
let unblock_predecessors =
|
let unblock_predecessors = unblock_together.entry(n).or_default();
|
||||||
unblock_together.entry(n).or_insert_with(HashSet::new);
|
|
||||||
unblock_stack.extend(unblock_predecessors.iter());
|
unblock_stack.extend(unblock_predecessors.iter());
|
||||||
unblock_predecessors.clear();
|
unblock_predecessors.clear();
|
||||||
}
|
}
|
||||||
@ -329,10 +328,7 @@ where
|
|||||||
} else {
|
} else {
|
||||||
// if its descendants can be unblocked later, this node will be too
|
// if its descendants can be unblocked later, this node will be too
|
||||||
for successor in subgraph.neighbors(*node) {
|
for successor in subgraph.neighbors(*node) {
|
||||||
unblock_together
|
unblock_together.entry(successor).or_default().insert(*node);
|
||||||
.entry(successor)
|
|
||||||
.or_insert_with(HashSet::new)
|
|
||||||
.insert(*node);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2375,8 +2375,8 @@ unsafe fn remove_bundle_from_archetype(
|
|||||||
|
|
||||||
// sort removed components so we can do an efficient "sorted remove". archetype
|
// sort removed components so we can do an efficient "sorted remove". archetype
|
||||||
// components are already sorted
|
// components are already sorted
|
||||||
removed_table_components.sort();
|
removed_table_components.sort_unstable();
|
||||||
removed_sparse_set_components.sort();
|
removed_sparse_set_components.sort_unstable();
|
||||||
next_table_components = current_archetype.table_components().collect();
|
next_table_components = current_archetype.table_components().collect();
|
||||||
next_sparse_set_components = current_archetype.sparse_set_components().collect();
|
next_sparse_set_components = current_archetype.sparse_set_components().collect();
|
||||||
sorted_remove(&mut next_table_components, &removed_table_components);
|
sorted_remove(&mut next_table_components, &removed_table_components);
|
||||||
|
Loading…
Reference in New Issue
Block a user