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

Simplify parallel iteration methods #8854

Merged
merged 9 commits into from
Jul 23, 2023
Merged

Conversation

JoJoJet
Copy link
Member

@JoJoJet JoJoJet commented Jun 16, 2023

Objective

The QueryParIter::for_each_mut function is required when doing parallel iteration with mutable queries.
This results in an unfortunate stutter: query.par_iter_mut().par_for_each_mut() ('mut' is repeated).

Solution

  • Make for_each compatible with mutable queries, and deprecate for_each_mut. In order to prevent for_each from being called multiple times in parallel, we take ownership of the QueryParIter.

Changelog

  • QueryParIter::for_each is now compatible with mutable queries. for_each_mut has been deprecated as it is now redundant.

Migration Guide

The method QueryParIter::for_each_mut has been deprecated and is no longer functional. Use for_each instead, which now supports mutable queries.

// Before:
query.par_iter_mut().for_each_mut(|x| ...);

// After:
query.par_iter_mut().for_each(|x| ...);

The method QueryParIter::for_each now takes ownership of the QueryParIter, rather than taking a shared reference.

// Before:
let par_iter = my_query.par_iter().batching_strategy(my_batching_strategy);
par_iter.for_each(|x| {
    // ...Do stuff with x...
    par_iter.for_each(|y| {
        // ...Do nested stuff with y...
    });
});

// After:
my_query.par_iter().batching_strategy(my_batching_strategy).for_each(|x| {
    // ...Do stuff with x...
    my_query.par_iter().batching_strategy(my_batching_strategy).for_each(|y| {
        // ...Do nested stuff with y...
    });
});

@JoJoJet JoJoJet added A-ECS Entities, components, systems, and events C-Usability A targeted quality-of-life change that makes Bevy easier to use M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide D-Complex Quite challenging from either a design or technical perspective. Ask for help! labels Jun 16, 2023
@JoJoJet
Copy link
Member Author

JoJoJet commented Jun 16, 2023

Marking this as complex since there is a decent chance that I've missed some unsoundness.

@SkiFire13
Copy link
Contributor

In order to prevent for_each from being called multiple times in parallel, we ensure that QueryParIter is !Sync for mutable queries.

Couldn't it just consume self instead? Iterator::for_each and rayon::ParallelIterator::for_each both consume self and it makes sense because after that you probably don't care about the iterator anymore. And if you do you can just create a new one since it's very cheap.

@JoJoJet
Copy link
Member Author

JoJoJet commented Jun 16, 2023

Couldn't it just consume self instead? Iterator::for_each and rayon::ParallelIterator::for_each both consume self and it makes sense because after that you probably don't care about the iterator anymore. And if you do you can just create a new one since it's very cheap.

Yes, that is another option.

@james7132
Copy link
Member

I'm much more comfortable having take ownership of QueryParIter over any borrow for the sake of soundness. This ensures there's no potential aliasing going on, and constructing a parallel iterator is by far the least expensive part of the operation, so there's very likely no significant perf blockers by doing so.

Note: you may also need to change the other builder methods like QueryParIter::batching_strategy to pass by ownership.

@SkiFire13
Copy link
Contributor

In order to prevent for_each from being called multiple times in parallel, we ensure that QueryParIter is !Sync for mutable queries.

Thinking more about it, this doesn't prevent for_each to be called multiple times reentrantly, which should be possible as long as this is done on the same thread that owns the QueryParIter (e.g. by using something like fragile).

@JoJoJet
Copy link
Member Author

JoJoJet commented Jun 20, 2023

I've modified for_each to take ownership, to fix the unsoundness that @SkiFire13 found.

crates/bevy_ecs/src/query/par_iter.rs Outdated Show resolved Hide resolved
@james7132 james7132 added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Jul 4, 2023
@mockersf
Copy link
Member

@JoJoJet could you fix the conflict and the comment above?

@JoJoJet
Copy link
Member Author

JoJoJet commented Jul 23, 2023

I've addressed James' comment and fixed the merge conflict.

@mockersf mockersf added this pull request to the merge queue Jul 23, 2023
Merged via the queue into bevyengine:main with commit ddbfa48 Jul 23, 2023
20 checks passed
@JoJoJet JoJoJet deleted the par-iter branch July 23, 2023 12:13
@cart cart mentioned this pull request Oct 13, 2023
43 tasks
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-Usability A targeted quality-of-life change that makes Bevy easier to use D-Complex Quite challenging from either a design or technical perspective. Ask for help! M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide 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