-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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 TrustedLen for Take<Repeat> and Take<RangeFrom> #47944
Conversation
Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @alexcrichton (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. Please see the contribution instructions for more information. |
@@ -325,6 +325,9 @@ impl<A: Step> Iterator for ops::RangeFrom<A> { | |||
#[unstable(feature = "fused", issue = "35602")] | |||
impl<A: Step> FusedIterator for ops::RangeFrom<A> {} | |||
|
|||
#[unstable(feature = "trusted_len", issue = "37572")] | |||
unsafe impl<A: Step> TrustedLen for ops::RangeFrom<A> {} | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh, this gets into the discussion about the "length of a x.. range" again.. I suppose it fits, it can panic before reaching the trusted length just as much as any_iterator.map(f)
can.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it panics, it diverges, which should be fine. Worst case is that e.g. a Vector is currently collecting the elements. This will leave it with uninitialized values, but it should be dropped anyway / it shouldn't be accessable anymore due to the panic.
Edit: Repeat
can panic as well if Clone
panics.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Linking previous discussion about why RangeFrom
needs to have an infinite size hint, which was also about Take<RangeFrom<_>>
: #42315 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@oberien Yes, checking again to make sure, Vec's special case using TrustedLen
trusts the length for the reallocation but it also behaves correctly on a possible panic in Iterator::next()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me. Please add tests for the new code paths.
I can't really test that the assembler will be optimized, can I? So I'll just add tests for the |
r? @bluss |
@oberien In fact you can. You can write a |
A test that exercises the new code path at all would be good, something that checks it produces the expected result. For example using |
Anything |
src/libcore/iter/mod.rs
Outdated
}; | ||
|
||
(lower, upper) | ||
TakeImpl::size_hint(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the specialization actually needed here? LLVM already collapses repeat(x).take(n).size_hint().0
=> n
today (play demo), since Repeat::size_hint
is a constant, so I'm not convinced the duplicate size_hint implementation is needed—just the TrustedLen
marker impls ought to be plenty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The specialization is needed to handle the case of inner.size_hint()
returning (x, None)
with x
being smaller than self.n
. With the non-specialized implementation this would result in Take::size_hint
returning (x, Some(self.n))
, which breaks the contract of TrustedLen
that the lower and upper bound must be equal if the upper bound is not None
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that allowed? I thought TrustedLen
could only have size_hint
s of (x, Some(x))
or (usize::MAX, None)
, since that's the most correct lower bound for "if the actual iterator length is larger than usize::MAX
".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@scottmcm Does the impl here break the contract? Please show an example if that's the case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The trait definition implies that the lower bound is irrelevant ( https://doc.rust-lang.org/std/iter/trait.TrustedLen.html )
The iterator reports a size hint where it is either exact (lower bound is equal to upper bound), or the upper bound is
None
. The upper bound must only be None if the actual iterator length is larger thanusize::MAX
.
If I recall correctly checks regarding this in libcore are only checking for the lower bound if there is an upper bound.
EDIT: For example SpecExtend
for Vec only has a check for the upper bound. They only have a debug_assert!
to check that the upper bound is equal to the lower bound if the upper bound is Some
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bluss I updated the documentation of TrustedLen
and removed the now unnecessary specialization of Take<TrustedLen>::size_hint
. Could you please double-check that all iterators implementing TrustedLen actually follow this rule? The only one I found is Range<{u,i}32>
if we're on a 16bit platform (i.e. 8bit, which should be negligible). I don't know if Rust is supporting any 16bit platform.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rust supports 16-bit
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I opened #48006 for this case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Survey of TrustedLen:
Simple: Empty (0,Some(0)); Once -> Option::IntoIter -> Item; the other two Option iterators also go to Item; the result iterators are like the option ones and are just 1 or 0; slice iterators are always finite by definition (they just return (.len(), Some(.len()))); vec IntoIter is also already bounded by usize
Forwards to inner: Rev, Cloned, Map, Enumerate, str::Bytes (to cloned slice iter), &mut I
Interesting: Chain makes things longer, and the addition is correct; Zip, as discussed, already required .0 == .1.unwrap_or(usize::MAX) for correctness
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Supporting chain was in the design of TrustedLen 😄
I think we should add to the documentation of TrustedLen that the iterator is also allowed to diverge. Currently it states
I think we should change it to
What do you think? |
806002e
to
45e63ae
Compare
#[no_mangle] | ||
pub fn range_from_take_collect() -> Vec<u8> { | ||
// CHECK: %broadcast.splatinsert = insertelement <{{[0-9]+}} x i8> undef, i8 %{{.*}}, i32 0 | ||
// CHECK: %broadcast.splat = shufflevector <[[WIDTH:[0-9]+]] x i8> %broadcast.splatinsert, <[[WIDTH]] x i8> undef, <[[WIDTH]] x i32> zeroinitializer |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should I use CHECK-NEXT
here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know the details here. Using CHECK-NEXT where possible seems very good. Are these the right instructions to match on? If it's too fragile or not meaningfully testing codegen it might be better to skip this function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test might be a bit fragile, but I don't know how else I'd test for llvm vectorizing the operations. In this test I'm checking that llvm extends the last written number into a vector, which should indicate with high certainty that it'll perform vectorized operations.
@oberien I think mentioning that the iterator can also panic before reaching the end is best, it is more clear. Just to avoid the confusion with an infinite iterator (which is not ok if a finite number of elements was promised.) |
#[no_mangle] | ||
pub fn repeat_take_collect() -> Vec<u8> { | ||
// CHECK: call void @llvm.memset.p0i8 | ||
iter::repeat(42).take(100000).collect() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🥇
Thanks, this is a very nice improvement! @bors r+ |
📌 Commit ee8b4ca has been approved by |
…n, r=bluss Implement TrustedLen for Take<Repeat> and Take<RangeFrom> This will allow optimization of simple `repeat(x).take(n).collect()` iterators, which are currently not vectorized and have capacity checks. This will only support a few aggregates on `Repeat` and `RangeFrom`, which might be enough for simple cases, but doesn't optimize more complex ones. Namely, Cycle, StepBy, Filter, FilterMap, Peekable, SkipWhile, Skip, FlatMap, Fuse and Inspect are not marked `TrustedLen` when the inner iterator is infinite. Previous discussion can be found in rust-lang#47082 r? @alexcrichton
@bors r- The new codegen test failed in i686-musl.
|
I guess I'm just going to delete that test then. It doesn't seem to be sophisticated enough after all. |
@bors r+ |
📌 Commit 6edd22e has been approved by |
@oberien Do you want to rebase (and squash some commits) this PR? |
3ec20f0
to
6caec2c
Compare
@bluss Done. Also a notifier for #47944 (comment) in case you missed that one (comment on outdated diff) |
Thanks. I'll have to come back to do the review of those impls a different evening. |
@@ -970,9 +970,11 @@ impl<'a, I: FusedIterator + ?Sized> FusedIterator for &'a mut I {} | |||
/// The iterator reports a size hint where it is either exact | |||
/// (lower bound is equal to upper bound), or the upper bound is [`None`]. | |||
/// The upper bound must only be [`None`] if the actual iterator length is | |||
/// larger than [`usize::MAX`]. | |||
/// larger than [`usize::MAX`]. In that case, the lower bound must be | |||
/// [`usize::MAX`], resulting in a [`.size_hint`] of `(usize::MAX, None)`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 to this. Probably worth mentioning in release notes just in case, though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How would I go about doing so? I don't know if it's really needed because TrustedLen is unstable anyways. But better safe than sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think for now it just means someone with tagging permissions (thus not me, at least) putting relnotes
on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't have an impact to users of stable Rust, so then it's normally not in any release notes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, makes sense. I guess nightly finds out by watching the TWiR PRs section.
Nice collaboration here. Since the identified bug is orthogonal, I think we should just let this merge. @bors r+ |
📌 Commit 6caec2c has been approved by |
…n, r=bluss Implement TrustedLen for Take<Repeat> and Take<RangeFrom> This will allow optimization of simple `repeat(x).take(n).collect()` iterators, which are currently not vectorized and have capacity checks. This will only support a few aggregates on `Repeat` and `RangeFrom`, which might be enough for simple cases, but doesn't optimize more complex ones. Namely, Cycle, StepBy, Filter, FilterMap, Peekable, SkipWhile, Skip, FlatMap, Fuse and Inspect are not marked `TrustedLen` when the inner iterator is infinite. Previous discussion can be found in rust-lang#47082 r? @alexcrichton
This will allow optimization of simple
repeat(x).take(n).collect()
iterators, which are currently not vectorized and have capacity checks.This will only support a few aggregates on
Repeat
andRangeFrom
, which might be enough for simple cases, but doesn't optimize more complex ones. Namely, Cycle, StepBy, Filter, FilterMap, Peekable, SkipWhile, Skip, FlatMap, Fuse and Inspect are not markedTrustedLen
when the inner iterator is infinite.Previous discussion can be found in #47082
r? @alexcrichton