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

implement FromEntitySetIterator #17513

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Victoronz
Copy link
Contributor

@Victoronz Victoronz commented Jan 23, 2025

Objective

Some collections are more efficient to construct when we know that every element is unique in advance.
We have EntitySetIterators from #16547, but currently no API to safely make use of them this way.

Solution

Add FromEntitySetIterator as a subtrait to FromIterator, and implement it for the EntityHashSet/hashbrown::HashSet types.
To match the normal FromIterator, we also add a EntitySetIterator::collect_set method.
It'd be better if these methods could shadow from_iter and collect completely, but rust-lang/rust#89151 is needed for that.

While currently only HashSets implement this trait, future UniqueEntityVec/UniqueEntitySlice functionality comes with more implementors.

Because HashMaps are collected from tuples instead of singular types, implementing this same optimization for them is more complex, and has to be done separately.

Showcase

This is basically a free speedup for collecting EntityHashSets!

pub fn collect_milk_dippers(dippers: Query<Entity, (With<Milk>, With<Cookies>)>) {
    dippers.iter().collect_set::<EntityHashSet>();
    // or
    EntityHashSet::from_entity_set_iter(dippers);
}

@Victoronz Victoronz added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events C-Performance A change motivated by improving speed, memory usage or compile times D-Straightforward Simple bug fixes and API improvements, docs, test and examples S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 23, 2025
@BenjaminBrienen BenjaminBrienen added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it D-Unsafe Touches with unsafe code in some way and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 23, 2025
Copy link
Contributor

@SpecificProtagonist SpecificProtagonist left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm… this is slower for me:

let mut world = World::new();
for _ in 0..1_000_000 {
    world.spawn_empty();
}
let mut query = world.query::<Entity>();
for _ in 0..10 {
    query.iter(&world).collect::<EntityHashSet>();
}

gives me

  Time (mean ± σ):      81.1 ms ±   0.9 ms    [User: 56.1 ms, System: 24.5 ms]
  Range (min … max):    80.0 ms …  83.7 ms    36 runs

while doing the same with collect_set gives

  Time (mean ± σ):     221.8 ms ±   4.3 ms    [User: 108.1 ms, System: 112.2 ms]
  Range (min … max):   217.2 ms … 231.5 ms    13 runs

This seems to be because it doesn't use size_hint. With that fixed it improves to

  Time (mean ± σ):      79.1 ms ±   2.3 ms    [User: 54.8 ms, System: 23.9 ms]
  Range (min … max):    77.7 ms …  92.1 ms    37 runs

or slightly faster than the standard collect (the spawning part of this benchmark takes about 26ms, with this PR improving the iter/collect part from ~49ms to ~47ms). Not a huge difference :/

Comment on lines +328 to +329
let mut set = HashSet::<T, S>::default();
for e in set_iter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut set = HashSet::<T, S>::default();
for e in set_iter {
let iter = set_iter.into_iter();
let mut set = HashSet::<T, S>::with_capacity_and_hasher(iter.size_hint().0, S::default());
for e in iter {

Comment on lines +200 to +201
let mut set = EntityHashSet::new();
for e in set_iter {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let mut set = EntityHashSet::new();
for e in set_iter {
let iter = set_iter.into_iter();
let mut set = EntityHashSet::with_capacity(iter.size_hint().0);
for e in iter {

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Feature A new feature, making something new possible C-Performance A change motivated by improving speed, memory usage or compile times D-Straightforward Simple bug fixes and API improvements, docs, test and examples D-Unsafe Touches with unsafe code in some way S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants