diff options
| author | Huon Wilson <dbau.pp+github@gmail.com> | 2015-04-08 14:25:22 +1000 |
|---|---|---|
| committer | Huon Wilson <dbau.pp+github@gmail.com> | 2015-04-10 14:42:17 +1000 |
| commit | c2258d6d042353bfa6daad6008bcd3c0af3f13de (patch) | |
| tree | fcba6d8badc0d442aea89d98f8b3470b26df2b60 /src/libcore/iter.rs | |
| parent | d9146bf8ba0bdf98a46c4656899e54802e96ac0c (diff) | |
| download | rust-c2258d6d042353bfa6daad6008bcd3c0af3f13de.tar.gz rust-c2258d6d042353bfa6daad6008bcd3c0af3f13de.zip | |
Optimise Iterator::{max, max_by, min, min_by}.
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/
Diffstat (limited to 'src/libcore/iter.rs')
| -rw-r--r-- | src/libcore/iter.rs | 93 |
1 files changed, 57 insertions, 36 deletions
diff --git a/src/libcore/iter.rs b/src/libcore/iter.rs index 939d9b4ab0e..8feae0bfb09 100644 --- a/src/libcore/iter.rs +++ b/src/libcore/iter.rs @@ -743,12 +743,12 @@ pub trait Iterator { #[stable(feature = "rust1", since = "1.0.0")] fn max(self) -> Option<Self::Item> where Self: Sized, Self::Item: Ord { - self.fold(None, |max, y| { - match max { - None => Some(y), - Some(x) => Some(cmp::max(x, y)) - } - }) + select_fold1(self, + |_| (), + // switch to y even if it is only equal, to preserve + // stability. + |_, x, _, y| *x <= *y) + .map(|(_, x)| x) } /// Consumes the entire iterator to return the minimum element. @@ -766,12 +766,12 @@ pub trait Iterator { #[stable(feature = "rust1", since = "1.0.0")] fn min(self) -> Option<Self::Item> where Self: Sized, Self::Item: Ord { - self.fold(None, |min, y| { - match min { - None => Some(y), - Some(x) => Some(cmp::min(x, y)) - } - }) + select_fold1(self, + |_| (), + // only switch to y if it is strictly smaller, to + // preserve stability. + |_, x, _, y| *x > *y) + .map(|(_, x)| x) } /// `min_max` finds the minimum and maximum elements in the iterator. @@ -869,21 +869,16 @@ pub trait Iterator { #[inline] #[unstable(feature = "core", 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, F: FnMut(&Self::Item) -> B, { - self.fold(None, |max: Option<(Self::Item, B)>, y| { - let y_val = f(&y); - match max { - None => Some((y, y_val)), - Some((x, x_val)) => if y_val >= x_val { - Some((y, y_val)) - } else { - Some((x, x_val)) - } - } - }).map(|(x, _)| x) + select_fold1(self, + f, + // switch to y even if it is only equal, to preserve + // stability. + |x_p, _, y_p, _| x_p <= y_p) + .map(|(_, x)| x) } /// Return the element that gives the minimum value from the @@ -903,21 +898,16 @@ pub trait Iterator { #[inline] #[unstable(feature = "core", 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, F: FnMut(&Self::Item) -> B, { - self.fold(None, |min: Option<(Self::Item, B)>, y| { - let y_val = f(&y); - match min { - None => Some((y, y_val)), - Some((x, x_val)) => if x_val <= y_val { - Some((x, x_val)) - } else { - Some((y, y_val)) - } - } - }).map(|(x, _)| x) + select_fold1(self, + f, + // only switch to y if it is strictly smaller, to + // preserve stability. + |x_p, _, y_p, _| x_p > y_p) + .map(|(_, x)| x) } /// Change the direction of the iterator @@ -1024,6 +1014,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")] impl<'a, I: Iterator + ?Sized> Iterator for &'a mut I { type Item = I::Item; |
