Auto merge of #96350 - austinabell:skip_optimization, r=Mark-Simulacrum
fix(iter::skip): Optimize `next` and `nth` implementations of `Skip` This avoids calling nth/next or nth/nth to first skip elements and then get the next one (unless necessary due to usize overflow).
This commit is contained in:
commit
80ed61fbd6
2 changed files with 50 additions and 8 deletions
|
@ -33,21 +33,32 @@ where
|
||||||
#[inline]
|
#[inline]
|
||||||
fn next(&mut self) -> Option<I::Item> {
|
fn next(&mut self) -> Option<I::Item> {
|
||||||
if unlikely(self.n > 0) {
|
if unlikely(self.n > 0) {
|
||||||
self.iter.nth(crate::mem::take(&mut self.n) - 1)?;
|
self.iter.nth(crate::mem::take(&mut self.n))
|
||||||
}
|
} else {
|
||||||
self.iter.next()
|
self.iter.next()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn nth(&mut self, n: usize) -> Option<I::Item> {
|
fn nth(&mut self, n: usize) -> Option<I::Item> {
|
||||||
// Can't just add n + self.n due to overflow.
|
|
||||||
if self.n > 0 {
|
if self.n > 0 {
|
||||||
let to_skip = self.n;
|
let skip: usize = crate::mem::take(&mut self.n);
|
||||||
self.n = 0;
|
// Checked add to handle overflow case.
|
||||||
// nth(n) skips n+1
|
let n = match skip.checked_add(n) {
|
||||||
self.iter.nth(to_skip - 1)?;
|
Some(nth) => nth,
|
||||||
|
None => {
|
||||||
|
// In case of overflow, load skip value, before loading `n`.
|
||||||
|
// Because the amount of elements to iterate is beyond `usize::MAX`, this
|
||||||
|
// is split into two `nth` calls where the `skip` `nth` call is discarded.
|
||||||
|
self.iter.nth(skip - 1)?;
|
||||||
|
n
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
// Load nth element including skip.
|
||||||
self.iter.nth(n)
|
self.iter.nth(n)
|
||||||
|
} else {
|
||||||
|
self.iter.nth(n)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -201,3 +201,34 @@ fn test_skip_non_fused() {
|
||||||
// advance it further. `Unfuse` tests that this doesn't happen by panicking in that scenario.
|
// advance it further. `Unfuse` tests that this doesn't happen by panicking in that scenario.
|
||||||
let _ = non_fused.skip(20).next();
|
let _ = non_fused.skip(20).next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_skip_non_fused_nth_overflow() {
|
||||||
|
let non_fused = Unfuse::new(0..10);
|
||||||
|
|
||||||
|
// Ensures that calling skip and `nth` where the sum would overflow does not fail for non-fused
|
||||||
|
// iterators.
|
||||||
|
let _ = non_fused.skip(20).nth(usize::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_skip_overflow_wrapping() {
|
||||||
|
// Test to ensure even on overflowing on `skip+nth` the correct amount of elements are yielded.
|
||||||
|
struct WrappingIterator(usize);
|
||||||
|
|
||||||
|
impl Iterator for WrappingIterator {
|
||||||
|
type Item = usize;
|
||||||
|
|
||||||
|
fn next(&mut self) -> core::option::Option<Self::Item> {
|
||||||
|
<Self as Iterator>::nth(self, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nth(&mut self, nth: usize) -> core::option::Option<Self::Item> {
|
||||||
|
self.0 = self.0.wrapping_add(nth.wrapping_add(1));
|
||||||
|
Some(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let wrap = WrappingIterator(0);
|
||||||
|
assert_eq!(wrap.skip(20).nth(usize::MAX), Some(20));
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue