-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Remove the need for collect_inner()
on QuerySortedManyIter
#16650
base: main
Are you sure you want to change the base?
Remove the need for collect_inner()
on QuerySortedManyIter
#16650
Conversation
This allows sound mutable iteration without needing to call collect_inner().
I don't think this works? |
It does work! That's what the miri test checks. I'm happy to add more tests if you can think of a case I missed. Lifetimes are erased in the abstract machine, so a borrow only needs to be valid when it's part of a live value. If the original lifetime mattered then the existing code would already be unsound, since it creates Once you place values in a
It's true that Would you think it would work if we used |
For what it's worth, |
The miri test should spawn more than 1 item, otherwise the sort doesn't have to do any comparisons/worry about aliasing. While it does sound more convincing, I want read up more on the safety of this first.
True! Setting aside safety, it is true that we want to avoid the allocation from a collect, but the sorts themselves are still the most expensive part in all of this, and degrading their performance would make the gain moot. |
I do pass that one entity twice in each sort ( What would you like the test would do with the other entity? Sort
I wouldn't expect any of these changes to impact performance, since the new code is entirely no-ops at runtime. But it's hard to tell without testing! Using |
Let's do it the simpler way and just do the collect! I think I'm more comfortable with that after dwelling on it for a bit.
The |
Alright, I'll put together a PR for that!
Yup! But in #15396 I want to let query items borrow from the state. The lens state is a local variable, so that means ending the lens borrows before returning. So whatever we do to end the lens borrows in |
Yeah, for the |
Objective
When calling any of the
sort
methods on aQueryManyIter
with mutable data,collect_inner()
must be called before fetching items. Remove the need for that call, improving both ergonomics and performance.Solution
The reason for
collect_inner()
was to avoid aliasing issues between theL::Item
s used during sorting and theD::Item
s returned from fetching. Instead, we wrap theL::Item
in aMaybeUninit
to avoid aliasing issues.L::Item
andD::Item
may have conflicting access, so we must ensure that we never have both alive for the same entity. The same entity may appear multiple times inself.entity_iter
, so we must ensure that noL::Item
is alive when we createD::Item
s infetch_next
. Theentity_list
we return is aMap<vec::IntoIter>
that owns the contents ofkeyed_query
, so it would keep theL::Item
s alive if we store them inkeyed_query
directly.The safest solution is to
collect()
theMap
into a freshVec<Entity>
, but we want to avoid the overhead of doing a new allocation and copying the data. We could re-use a single allocation by storingOption<L::Item>
and setting them toNone
, but that would still have overhead of writing theNone
values.Instead, we store
MaybeUninit<L::Item>
, which acts similar toOption
. The compiler can never assume the item is initialized, so we don't need to do anything to uninitialize it.(If this works, it can also help unblock #15396. One of the issues there is that the
sort()
methods hold aL::Item
that would borrow from the query state. If we can make those be uninitializedMaybeUninit<L::Item>
s, then we could safely transmute away the lifetimes.)Testing
Added a test that creates a
QuerySortedManyIter
over mutable data with duplicateEntity
s. Without theMaybeUninit
wrapper, running it through miri fails with an aliasing violation. With theMaybeUninit
wrapper, it passes.