Skip to content

Commit 9e67bc3

Browse files
committed
extra: simplify the bench stat loop, improve stability somewhat (?)
1 parent fbc5bb4 commit 9e67bc3

File tree

2 files changed

+46
-53
lines changed

2 files changed

+46
-53
lines changed

src/libextra/stats.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ pub trait Stats {
100100
}
101101

102102
/// Extracted collection of all the summary statistics of a sample set.
103+
#[deriving(Eq)]
103104
struct Summary {
104105
sum: f64,
105106
min: f64,
@@ -116,7 +117,9 @@ struct Summary {
116117
}
117118

118119
impl Summary {
119-
fn new(samples: &[f64]) -> Summary {
120+
121+
/// Construct a new summary of a sample set.
122+
pub fn new(samples: &[f64]) -> Summary {
120123
Summary {
121124
sum: samples.sum(),
122125
min: samples.min(),

src/libextra/test.rs

Lines changed: 42 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,20 @@
1818

1919
use getopts;
2020
use sort;
21+
use stats;
2122
use stats::Stats;
2223
use term;
2324
use time::precise_time_ns;
2425

2526
use std::comm::{stream, SharedChan};
2627
use std::either;
2728
use std::io;
28-
use std::num;
2929
use std::option;
30-
use std::rand::RngUtil;
31-
use std::rand;
3230
use std::result;
3331
use std::task;
3432
use std::to_str::ToStr;
3533
use std::u64;
3634
use std::uint;
37-
use std::vec;
3835

3936

4037
// The name of a test. By convention this follows the rules for rust
@@ -184,7 +181,7 @@ pub fn parse_opts(args: &[~str]) -> OptRes {
184181

185182
#[deriving(Eq)]
186183
pub struct BenchSamples {
187-
ns_iter_samples: ~[f64],
184+
ns_iter_summ: stats::Summary,
188185
mb_s: uint
189186
}
190187

@@ -299,16 +296,15 @@ pub fn run_tests_console(opts: &TestOpts,
299296
return success;
300297

301298
fn fmt_bench_samples(bs: &BenchSamples) -> ~str {
302-
use stats::Stats;
303299
if bs.mb_s != 0 {
304300
fmt!("%u ns/iter (+/- %u) = %u MB/s",
305-
bs.ns_iter_samples.median() as uint,
306-
3 * (bs.ns_iter_samples.median_abs_dev() as uint),
301+
bs.ns_iter_summ.median as uint,
302+
(bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint,
307303
bs.mb_s)
308304
} else {
309305
fmt!("%u ns/iter (+/- %u)",
310-
bs.ns_iter_samples.median() as uint,
311-
3 * (bs.ns_iter_samples.median_abs_dev() as uint))
306+
bs.ns_iter_summ.median as uint,
307+
(bs.ns_iter_summ.max - bs.ns_iter_summ.min) as uint)
312308
}
313309
}
314310

@@ -688,54 +684,48 @@ impl BenchHarness {
688684
}
689685
}
690686
691-
// This is a more statistics-driven benchmark algorithm.
692-
// It stops as quickly as 50ms, so long as the statistical
693-
// properties are satisfactory. If those properties are
694-
// not met, it may run as long as the Go algorithm.
695-
pub fn auto_bench(&mut self, f: &fn(&mut BenchHarness)) -> ~[f64] {
687+
// This is a more statistics-driven benchmark algorithm. It stops as
688+
// quickly as 100ms, so long as the statistical properties are
689+
// satisfactory. If those properties are not met, it may run as long as
690+
// the Go algorithm.
691+
pub fn auto_bench(&mut self, f: &fn(&mut BenchHarness)) -> stats::Summary {
696692
697-
let mut rng = rand::rng();
698-
let mut magnitude = 10;
699-
let mut prev_madp = 0.0;
693+
let mut magnitude = 1000;
700694
695+
let samples : &mut [f64] = [0.0_f64, ..100];
701696
loop {
702-
let n_samples = rng.gen_uint_range(50, 60);
703-
let n_iter = rng.gen_uint_range(magnitude,
704-
magnitude * 2);
697+
let loop_start = precise_time_ns();
705698
706-
let samples = do vec::from_fn(n_samples) |_| {
707-
self.bench_n(n_iter as u64, |x| f(x));
708-
self.ns_per_iter() as f64
699+
for samples.mut_iter().advance() |p| {
700+
self.bench_n(magnitude as u64, |x| f(x));
701+
*p = self.ns_per_iter() as f64;
709702
};
710703
711-
// Eliminate outliers
712-
let med = samples.median();
713-
let mad = samples.median_abs_dev();
714-
let samples = do samples.consume_iter().filter |f| {
715-
num::abs(*f - med) <= 3.0 * mad
716-
}.collect::<~[f64]>();
717-
718-
debug!("%u samples, median %f, MAD=%f, %u survived filter",
719-
n_samples, med as float, mad as float,
720-
samples.len());
721-
722-
if samples.len() != 0 {
723-
// If we have _any_ cluster of signal...
724-
let curr_madp = samples.median_abs_dev_pct();
725-
if self.ns_elapsed() > 1_000_000 &&
726-
(curr_madp < 1.0 ||
727-
num::abs(curr_madp - prev_madp) < 0.1) {
728-
return samples;
729-
}
730-
prev_madp = curr_madp;
704+
// Clip top 10% and bottom 10% of outliers
705+
stats::winsorize(samples, 10.0);
706+
let summ = stats::Summary::new(samples);
731707
732-
if n_iter > 20_000_000 ||
733-
self.ns_elapsed() > 20_000_000 {
734-
return samples;
735-
}
708+
debug!("%u samples, median %f, MAD=%f, MADP=%f",
709+
samples.len(),
710+
summ.median as float,
711+
summ.median_abs_dev as float,
712+
summ.median_abs_dev_pct as float);
713+
714+
let now = precise_time_ns();
715+
let loop_run = now - loop_start;
716+
717+
// Stop early if we have a good signal after a 100ms loop.
718+
if loop_run > 100_000_000 && summ.median_abs_dev_pct < 5.0 {
719+
return summ;
720+
}
721+
722+
// Longest we ever run for is 1s.
723+
if loop_run > 1_000_000_000 {
724+
return summ;
736725
}
737726
738-
magnitude *= 2;
727+
magnitude *= 3;
728+
magnitude /= 2;
739729
}
740730
}
741731
}
@@ -752,13 +742,13 @@ pub mod bench {
752742
bytes: 0
753743
};
754744
755-
let ns_iter_samples = bs.auto_bench(f);
745+
let ns_iter_summ = bs.auto_bench(f);
756746
757-
let iter_s = 1_000_000_000 / (ns_iter_samples.median() as u64);
747+
let iter_s = 1_000_000_000 / (ns_iter_summ.median as u64);
758748
let mb_s = (bs.bytes * iter_s) / 1_000_000;
759749
760750
BenchSamples {
761-
ns_iter_samples: ns_iter_samples,
751+
ns_iter_summ: ns_iter_summ,
762752
mb_s: mb_s as uint
763753
}
764754
}

0 commit comments

Comments
 (0)