Skip to content

Commit

Permalink
Move test::Bencher to a new (unstable) std::bench module
Browse files Browse the repository at this point in the history
This is a first step toward stabilizing it and the `#[bench]` attribute:
rust-lang#66287

To avoid also moving much of the `test` crate `Bencher` is now only
responsible for runnning user code and measuring the time it takes,
not anymore for doing statistical analysis.
This separation is based on `&mut dyn FnMut` callbacks.

This introduces dynamic dispatch, which in general could affect performance
characteristics of a program. However I expect benchmarking results not to be
affected here since the {`Instant::new`; user code; `Instant::elapsed`}
sequence is kept monomorphized and not crossing a dynamic dispatch boundary.

This also adds a lifetime parameter to the `Bencher` struct,
which is is technically a breaking change to an unstable type.
I expect the impact to be low on existing users: only those with
`#[deny(warnings)]` or `#[deny(elided_lifetimes_in_paths)]`
would need to change their code. (See next commit.)
This lint is `allow` by default.
  • Loading branch information
SimonSapin committed Nov 11, 2019
1 parent 3fc30d8 commit 2018b69
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 87 deletions.
55 changes: 55 additions & 0 deletions src/libstd/bench.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//! Benchmarking
#![unstable(feature = "test", issue = "50297")]

use crate::hint::black_box;
use crate::time::{Instant, Duration};


/// Manager of the benchmarking runs.
///
/// This is fed into functions marked with `#[bench]` to allow for
/// set-up & tear-down before running a piece of code repeatedly via a
/// call to `iter`.
#[allow(missing_debug_implementations)]
pub struct Bencher<'a> {
callback: Callback<'a>,
/// FIXME
pub bytes: u64,
}

impl<'a> Bencher<'a> {
/// Callback for benchmark functions to run in their body.
pub fn iter<T>(&mut self, mut f: impl FnMut() -> T) {
(self.callback)(self.bytes, &mut |n_iterations| {
let start = Instant::now();
for _ in 0..n_iterations {
black_box(f());
}
ns_from_dur(start.elapsed())
})
}
}

fn ns_from_dur(dur: Duration) -> u64 {
dur.as_secs() * 1_000_000_000 + (dur.subsec_nanos() as u64)
}

// Permanently-unstable implementation details, only public for use by the `test` crate:

/// n_iterations -> nanoseconds
#[doc(hidden)]
#[unstable(feature = "test_internals", issue = "0")]
pub type TimeIterations<'i> = &'i mut dyn FnMut(u64) -> u64;

#[doc(hidden)]
#[unstable(feature = "test_internals", issue = "0")]
pub type Callback<'a> = &'a mut dyn FnMut(/* bytes: */ u64, TimeIterations<'_>);

#[doc(hidden)]
#[unstable(feature = "test_internals", issue = "0")]
impl<'a> Bencher<'a> {
pub fn new(callback: Callback<'a>) -> Self {
Self { callback, bytes: 0 }
}
}
1 change: 1 addition & 0 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ pub mod f64;
pub mod thread;
pub mod ascii;
pub mod backtrace;
pub mod bench;
pub mod collections;
pub mod env;
pub mod error;
Expand Down
98 changes: 21 additions & 77 deletions src/libtest/bench.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Benchmarking module.
pub use std::bench::{Bencher, TimeIterations};
pub use std::hint::black_box;

use super::{
event::CompletedTest,
helpers::sink::Sink,
options::BenchMode,
types::TestDesc,
test_result::TestResult,
Sender,
Expand All @@ -17,41 +17,6 @@ use std::io;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::{Arc, Mutex};

/// Manager of the benchmarking runs.
///
/// This is fed into functions marked with `#[bench]` to allow for
/// set-up & tear-down before running a piece of code repeatedly via a
/// call to `iter`.
#[derive(Clone)]
pub struct Bencher {
mode: BenchMode,
summary: Option<stats::Summary>,
pub bytes: u64,
}

impl Bencher {
/// Callback for benchmark functions to run in their body.
pub fn iter<T, F>(&mut self, mut inner: F)
where
F: FnMut() -> T,
{
if self.mode == BenchMode::Single {
ns_iter_inner(&mut inner, 1);
return;
}

self.summary = Some(iter(&mut inner));
}

pub fn bench<F>(&mut self, mut f: F) -> Option<stats::Summary>
where
F: FnMut(&mut Bencher),
{
f(self);
self.summary
}
}

#[derive(Debug, Clone, PartialEq)]
pub struct BenchSamples {
pub ns_iter_summ: stats::Summary,
Expand Down Expand Up @@ -104,27 +69,9 @@ fn fmt_thousands_sep(mut n: usize, sep: char) -> String {
output
}

fn ns_from_dur(dur: Duration) -> u64 {
dur.as_secs() * 1_000_000_000 + (dur.subsec_nanos() as u64)
}

fn ns_iter_inner<T, F>(inner: &mut F, k: u64) -> u64
where
F: FnMut() -> T,
{
let start = Instant::now();
for _ in 0..k {
black_box(inner());
}
ns_from_dur(start.elapsed())
}

pub fn iter<T, F>(inner: &mut F) -> stats::Summary
where
F: FnMut() -> T,
{
pub fn run_and_summarize(inner: TimeIterations<'_>) -> stats::Summary {
// Initial bench run to get ballpark figure.
let ns_single = ns_iter_inner(inner, 1);
let ns_single = inner(1);

// Try to estimate iter count for 1ms falling back to 1m
// iterations if first run took < 1ns.
Expand All @@ -144,14 +91,14 @@ where
let loop_start = Instant::now();

for p in &mut *samples {
*p = ns_iter_inner(inner, n) as f64 / n as f64;
*p = inner(n) as f64 / n as f64;
}

stats::winsorize(samples, 5.0);
let summ = stats::Summary::new(samples);

for p in &mut *samples {
let ns = ns_iter_inner(inner, 5 * n);
let ns = inner(5 * n);
*p = ns as f64 / (5 * n) as f64;
}

Expand Down Expand Up @@ -188,16 +135,10 @@ where
}
}

pub fn benchmark<F>(desc: TestDesc, monitor_ch: Sender<CompletedTest>, nocapture: bool, f: F)
pub fn benchmark<F>(desc: TestDesc, monitor_ch: Sender<CompletedTest>, nocapture: bool, mut f: F)
where
F: FnMut(&mut Bencher),
F: FnMut(&mut Bencher<'_>),
{
let mut bs = Bencher {
mode: BenchMode::Auto,
summary: None,
bytes: 0,
};

let data = Arc::new(Mutex::new(Vec::new()));
let oldio = if !nocapture {
Some((
Expand All @@ -208,7 +149,13 @@ where
None
};

let result = catch_unwind(AssertUnwindSafe(|| bs.bench(f)));
let result = catch_unwind(AssertUnwindSafe(|| {
let mut summary = None;
f(&mut Bencher::new(&mut |bytes, iter| {
summary = Some((bytes, run_and_summarize(iter)));
}));
summary
}));

if let Some((printio, panicio)) = oldio {
io::set_print(printio);
Expand All @@ -217,9 +164,9 @@ where

let test_result = match result {
//bs.bench(f) {
Ok(Some(ns_iter_summ)) => {
Ok(Some((bytes, ns_iter_summ))) => {
let ns_iter = cmp::max(ns_iter_summ.median as u64, 1);
let mb_s = bs.bytes * 1000 / ns_iter;
let mb_s = bytes * 1000 / ns_iter;

let bs = BenchSamples {
ns_iter_summ,
Expand All @@ -245,14 +192,11 @@ where
monitor_ch.send(message).unwrap();
}

pub fn run_once<F>(f: F)
pub fn run_once<F>(mut f: F)
where
F: FnMut(&mut Bencher),
F: FnMut(&mut Bencher<'_>),
{
let mut bs = Bencher {
mode: BenchMode::Single,
summary: None,
bytes: 0,
};
bs.bench(f);
f(&mut Bencher::new(&mut |_bytes, iter| {
iter(1);
}));
}
1 change: 1 addition & 0 deletions src/libtest/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#![feature(staged_api)]
#![feature(termination_trait_lib)]
#![feature(test)]
#![feature(test_internals)]

// Public reexports
pub use self::ColorConfig::*;
Expand Down
7 changes: 0 additions & 7 deletions src/libtest/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,6 @@ pub enum Concurrent {
No,
}

/// Number of times to run a benchmarked function
#[derive(Clone, PartialEq, Eq)]
pub enum BenchMode {
Auto,
Single,
}

/// Whether test is expected to panic or not
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ShouldPanic {
Expand Down
4 changes: 2 additions & 2 deletions src/test/ui/issues/issue-12997-1.stderr
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
error: functions used as benches must have signature `fn(&mut Bencher) -> impl Termination`
error: functions used as benches must have signature `fn(&mut Bencher<'_>) -> impl Termination`
--> $DIR/issue-12997-1.rs:8:1
|
LL | fn foo() { }
| ^^^^^^^^^^^^

error: functions used as benches must have signature `fn(&mut Bencher) -> impl Termination`
error: functions used as benches must have signature `fn(&mut Bencher<'_>) -> impl Termination`
--> $DIR/issue-12997-1.rs:11:1
|
LL | fn bar(x: isize, y: isize) { }
Expand Down
2 changes: 1 addition & 1 deletion src/test/ui/issues/issue-12997-2.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ LL | fn bar(x: isize) { }
| ^^^^^^^^^^^^^^^^^^^^ expected isize, found mutable reference
|
= note: expected type `isize`
found type `&mut test::Bencher`
found type `&mut test::Bencher<'_>`

error: aborting due to previous error

Expand Down

0 comments on commit 2018b69

Please sign in to comment.