Auto merge of #24180 - huonw:optimise-max-etc, r=alexcrichton

The main change in this patch is removing the use of `Option` inside the
inner loops of those functions to avoid comparisons where one branch
will only trigger on the first pass through the loop.

The included benchmarks go from:

    test bench_max    ... bench:       372 ns/iter (+/- 118)
    test bench_max_by ... bench:       428 ns/iter (+/- 33)
    test bench_max_by2 ... bench:      7128 ns/iter (+/- 326)

to:

    test bench_max    ... bench:       317 ns/iter (+/- 64)
    test bench_max_by ... bench:       356 ns/iter (+/- 270)
    test bench_max_by2 ... bench:      1387 ns/iter (+/- 183)

Problem noticed in http://www.reddit.com/r/rust/comments/31syce/using_iterators_to_find_the_index_of_the_min_or/
This commit is contained in:
bors 2015-04-10 07:54:18 +00:00
commit e4f9ddb878
2 changed files with 88 additions and 36 deletions

View File

@ -744,12 +744,12 @@ pub trait Iterator {
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
fn max(self) -> Option<Self::Item> where Self: Sized, Self::Item: Ord fn max(self) -> Option<Self::Item> where Self: Sized, Self::Item: Ord
{ {
self.fold(None, |max, y| { select_fold1(self,
match max { |_| (),
None => Some(y), // switch to y even if it is only equal, to preserve
Some(x) => Some(cmp::max(x, y)) // stability.
} |_, x, _, y| *x <= *y)
}) .map(|(_, x)| x)
} }
/// Consumes the entire iterator to return the minimum element. /// Consumes the entire iterator to return the minimum element.
@ -767,12 +767,12 @@ pub trait Iterator {
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
fn min(self) -> Option<Self::Item> where Self: Sized, Self::Item: Ord fn min(self) -> Option<Self::Item> where Self: Sized, Self::Item: Ord
{ {
self.fold(None, |min, y| { select_fold1(self,
match min { |_| (),
None => Some(y), // only switch to y if it is strictly smaller, to
Some(x) => Some(cmp::min(x, y)) // preserve stability.
} |_, x, _, y| *x > *y)
}) .map(|(_, x)| x)
} }
/// `min_max` finds the minimum and maximum elements in the iterator. /// `min_max` finds the minimum and maximum elements in the iterator.
@ -870,21 +870,16 @@ pub trait Iterator {
#[inline] #[inline]
#[unstable(feature = "core", #[unstable(feature = "core",
reason = "may want to produce an Ordering directly; see #15311")] reason = "may want to produce an Ordering directly; see #15311")]
fn max_by<B: Ord, F>(self, mut f: F) -> Option<Self::Item> where fn max_by<B: Ord, F>(self, f: F) -> Option<Self::Item> where
Self: Sized, Self: Sized,
F: FnMut(&Self::Item) -> B, F: FnMut(&Self::Item) -> B,
{ {
self.fold(None, |max: Option<(Self::Item, B)>, y| { select_fold1(self,
let y_val = f(&y); f,
match max { // switch to y even if it is only equal, to preserve
None => Some((y, y_val)), // stability.
Some((x, x_val)) => if y_val >= x_val { |x_p, _, y_p, _| x_p <= y_p)
Some((y, y_val)) .map(|(_, x)| x)
} else {
Some((x, x_val))
}
}
}).map(|(x, _)| x)
} }
/// Return the element that gives the minimum value from the /// Return the element that gives the minimum value from the
@ -904,21 +899,16 @@ pub trait Iterator {
#[inline] #[inline]
#[unstable(feature = "core", #[unstable(feature = "core",
reason = "may want to produce an Ordering directly; see #15311")] reason = "may want to produce an Ordering directly; see #15311")]
fn min_by<B: Ord, F>(self, mut f: F) -> Option<Self::Item> where fn min_by<B: Ord, F>(self, f: F) -> Option<Self::Item> where
Self: Sized, Self: Sized,
F: FnMut(&Self::Item) -> B, F: FnMut(&Self::Item) -> B,
{ {
self.fold(None, |min: Option<(Self::Item, B)>, y| { select_fold1(self,
let y_val = f(&y); f,
match min { // only switch to y if it is strictly smaller, to
None => Some((y, y_val)), // preserve stability.
Some((x, x_val)) => if x_val <= y_val { |x_p, _, y_p, _| x_p > y_p)
Some((x, x_val)) .map(|(_, x)| x)
} else {
Some((y, y_val))
}
}
}).map(|(x, _)| x)
} }
/// Change the direction of the iterator /// Change the direction of the iterator
@ -1066,6 +1056,37 @@ pub trait Iterator {
} }
} }
/// Select an element from an iterator based on the given projection
/// and "comparison" function.
///
/// This is an idiosyncratic helper to try to factor out the
/// commonalities of {max,min}{,_by}. In particular, this avoids
/// having to implement optimisations several times.
#[inline]
fn select_fold1<I,B, FProj, FCmp>(mut it: I,
mut f_proj: FProj,
mut f_cmp: FCmp) -> Option<(B, I::Item)>
where I: Iterator,
FProj: FnMut(&I::Item) -> B,
FCmp: FnMut(&B, &I::Item, &B, &I::Item) -> bool
{
// start with the first element as our selection. This avoids
// having to use `Option`s inside the loop, translating to a
// sizeable performance gain (6x in one case).
it.next().map(|mut sel| {
let mut sel_p = f_proj(&sel);
for x in it {
let x_p = f_proj(&x);
if f_cmp(&sel_p, &sel, &x_p, &x) {
sel = x;
sel_p = x_p;
}
}
(sel_p, sel)
})
}
#[stable(feature = "rust1", since = "1.0.0")] #[stable(feature = "rust1", since = "1.0.0")]
impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I { impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I {
type Item = I::Item; type Item = I::Item;

View File

@ -901,3 +901,34 @@ fn bench_multiple_take(b: &mut Bencher) {
} }
}); });
} }
fn scatter(x: i32) -> i32 { (x * 31) % 127 }
#[bench]
fn bench_max_by(b: &mut Bencher) {
b.iter(|| {
let it = 0..100;
it.max_by(|&x| scatter(x))
})
}
// http://www.reddit.com/r/rust/comments/31syce/using_iterators_to_find_the_index_of_the_min_or/
#[bench]
fn bench_max_by2(b: &mut Bencher) {
fn max_index_iter(array: &[i32]) -> usize {
array.iter().enumerate().max_by(|&(_, item)| item).unwrap().0
}
let mut data = vec![0i32; 1638];
data[514] = 9999;
b.iter(|| max_index_iter(&data));
}
#[bench]
fn bench_max(b: &mut Bencher) {
b.iter(|| {
let it = 0..100;
it.map(scatter).max()
})
}