Auto merge of #100845 - timvermeulen:iter_compare, r=scottmcm

Use internal iteration in `Iterator` comparison methods

Updates the `Iterator` methods `cmp_by`, `partial_cmp_by`, and `eq_by` to use internal iteration on `self`. I've also extracted their shared logic into a private helper function `iter_compare`, which will either short-circuit once the comparison result is known or return the comparison of the lengths of the iterators.

This change also indirectly benefits calls to `cmp`, `partial_cmp`, `eq`, `lt`, `le`, `gt`, and `ge`.

Unsurprising benchmark results: iterators that benefit from internal iteration (like `Chain`) see a speedup, while other iterators are unaffected.
```
 name                           before ns/iter  after ns/iter  diff ns/iter   diff %  speedup
 iter::bench_chain_partial_cmp  208,301         54,978             -153,323  -73.61%   x 3.79
 iter::bench_partial_cmp        55,527          55,702                  175    0.32%   x 1.00
 iter::bench_lt                 55,502          55,322                 -180   -0.32%   x 1.00
```
This commit is contained in:
bors 2022-09-24 04:04:46 +00:00
commit 06968954f7
2 changed files with 88 additions and 62 deletions

View File

@ -363,6 +363,13 @@ fn bench_partial_cmp(b: &mut Bencher) {
b.iter(|| (0..100000).map(black_box).partial_cmp((0..100000).map(black_box)))
}
#[bench]
fn bench_chain_partial_cmp(b: &mut Bencher) {
b.iter(|| {
(0..50000).chain(50000..100000).map(black_box).partial_cmp((0..100000).map(black_box))
})
}
#[bench]
fn bench_lt(b: &mut Bencher) {
b.iter(|| (0..100000).map(black_box).lt((0..100000).map(black_box)))

View File

@ -3461,36 +3461,27 @@ pub trait Iterator {
/// assert_eq!(xs.iter().cmp_by(&ys, |&x, &y| (2 * x).cmp(&y)), Ordering::Greater);
/// ```
#[unstable(feature = "iter_order_by", issue = "64295")]
fn cmp_by<I, F>(mut self, other: I, mut cmp: F) -> Ordering
fn cmp_by<I, F>(self, other: I, cmp: F) -> Ordering
where
Self: Sized,
I: IntoIterator,
F: FnMut(Self::Item, I::Item) -> Ordering,
{
let mut other = other.into_iter();
loop {
let x = match self.next() {
None => {
if other.next().is_none() {
return Ordering::Equal;
} else {
return Ordering::Less;
}
}
Some(val) => val,
};
let y = match other.next() {
None => return Ordering::Greater,
Some(val) => val,
};
match cmp(x, y) {
Ordering::Equal => (),
non_eq => return non_eq,
#[inline]
fn compare<X, Y, F>(mut cmp: F) -> impl FnMut(X, Y) -> ControlFlow<Ordering>
where
F: FnMut(X, Y) -> Ordering,
{
move |x, y| match cmp(x, y) {
Ordering::Equal => ControlFlow::CONTINUE,
non_eq => ControlFlow::Break(non_eq),
}
}
match iter_compare(self, other.into_iter(), compare(cmp)) {
ControlFlow::Continue(ord) => ord,
ControlFlow::Break(ord) => ord,
}
}
/// [Lexicographically](Ord#lexicographical-comparison) compares the elements of this [`Iterator`] with those
@ -3546,36 +3537,27 @@ pub trait Iterator {
/// );
/// ```
#[unstable(feature = "iter_order_by", issue = "64295")]
fn partial_cmp_by<I, F>(mut self, other: I, mut partial_cmp: F) -> Option<Ordering>
fn partial_cmp_by<I, F>(self, other: I, partial_cmp: F) -> Option<Ordering>
where
Self: Sized,
I: IntoIterator,
F: FnMut(Self::Item, I::Item) -> Option<Ordering>,
{
let mut other = other.into_iter();
loop {
let x = match self.next() {
None => {
if other.next().is_none() {
return Some(Ordering::Equal);
} else {
return Some(Ordering::Less);
}
}
Some(val) => val,
};
let y = match other.next() {
None => return Some(Ordering::Greater),
Some(val) => val,
};
match partial_cmp(x, y) {
Some(Ordering::Equal) => (),
non_eq => return non_eq,
#[inline]
fn compare<X, Y, F>(mut partial_cmp: F) -> impl FnMut(X, Y) -> ControlFlow<Option<Ordering>>
where
F: FnMut(X, Y) -> Option<Ordering>,
{
move |x, y| match partial_cmp(x, y) {
Some(Ordering::Equal) => ControlFlow::CONTINUE,
non_eq => ControlFlow::Break(non_eq),
}
}
match iter_compare(self, other.into_iter(), compare(partial_cmp)) {
ControlFlow::Continue(ord) => Some(ord),
ControlFlow::Break(ord) => ord,
}
}
/// Determines if the elements of this [`Iterator`] are equal to those of
@ -3613,29 +3595,26 @@ pub trait Iterator {
/// assert!(xs.iter().eq_by(&ys, |&x, &y| x * x == y));
/// ```
#[unstable(feature = "iter_order_by", issue = "64295")]
fn eq_by<I, F>(mut self, other: I, mut eq: F) -> bool
fn eq_by<I, F>(self, other: I, eq: F) -> bool
where
Self: Sized,
I: IntoIterator,
F: FnMut(Self::Item, I::Item) -> bool,
{
let mut other = other.into_iter();
loop {
let x = match self.next() {
None => return other.next().is_none(),
Some(val) => val,
};
let y = match other.next() {
None => return false,
Some(val) => val,
};
if !eq(x, y) {
return false;
#[inline]
fn compare<X, Y, F>(mut eq: F) -> impl FnMut(X, Y) -> ControlFlow<()>
where
F: FnMut(X, Y) -> bool,
{
move |x, y| {
if eq(x, y) { ControlFlow::CONTINUE } else { ControlFlow::BREAK }
}
}
match iter_compare(self, other.into_iter(), compare(eq)) {
ControlFlow::Continue(ord) => ord == Ordering::Equal,
ControlFlow::Break(()) => false,
}
}
/// Determines if the elements of this [`Iterator`] are unequal to those of
@ -3860,6 +3839,46 @@ pub trait Iterator {
}
}
/// Compares two iterators element-wise using the given function.
///
/// If `ControlFlow::CONTINUE` is returned from the function, the comparison moves on to the next
/// elements of both iterators. Returning `ControlFlow::Break(x)` short-circuits the iteration and
/// returns `ControlFlow::Break(x)`. If one of the iterators runs out of elements,
/// `ControlFlow::Continue(ord)` is returned where `ord` is the result of comparing the lengths of
/// the iterators.
///
/// Isolates the logic shared by ['cmp_by'](Iterator::cmp_by),
/// ['partial_cmp_by'](Iterator::partial_cmp_by), and ['eq_by'](Iterator::eq_by).
#[inline]
fn iter_compare<A, B, F, T>(mut a: A, mut b: B, f: F) -> ControlFlow<T, Ordering>
where
A: Iterator,
B: Iterator,
F: FnMut(A::Item, B::Item) -> ControlFlow<T>,
{
#[inline]
fn compare<'a, B, X, T>(
b: &'a mut B,
mut f: impl FnMut(X, B::Item) -> ControlFlow<T> + 'a,
) -> impl FnMut(X) -> ControlFlow<ControlFlow<T, Ordering>> + 'a
where
B: Iterator,
{
move |x| match b.next() {
None => ControlFlow::Break(ControlFlow::Continue(Ordering::Greater)),
Some(y) => f(x, y).map_break(ControlFlow::Break),
}
}
match a.try_for_each(compare(&mut b, f)) {
ControlFlow::Continue(()) => ControlFlow::Continue(match b.next() {
None => Ordering::Equal,
Some(_) => Ordering::Less,
}),
ControlFlow::Break(x) => x,
}
}
#[stable(feature = "rust1", since = "1.0.0")]
impl<I: Iterator + ?Sized> Iterator for &mut I {
type Item = I::Item;