Skip to content

Commit f5b9683

Browse files
authored
Time Measure & Support for Multiple Measures (#285)
* Add simple time measurement While imperfect, this is still useful data that anchors benchmark iterations to a number that is more meaningful to humans sometimes. Combined with the ability to capture multiple measures, this is pretty useful to have available. * Add support for capturing multiple measurements Previously, only a single measure type could be requested. Often, we might want to capture multiple measures on a benchmark run as different measurements might provide a more complete picture of a given benchmark iteration.
1 parent 40e0232 commit f5b9683

File tree

4 files changed

+90
-6
lines changed

4 files changed

+90
-6
lines changed

crates/cli/src/benchmark.rs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use rand::{rngs::SmallRng, Rng, SeedableRng};
44
use sightglass_data::{Format, Measurement, Phase};
55
use sightglass_recorder::bench_api::Engine;
66
use sightglass_recorder::cpu_affinity::bind_to_single_core;
7+
use sightglass_recorder::measure::multi::MultiMeasure;
78
use sightglass_recorder::measure::Measurements;
89
use sightglass_recorder::{bench_api::BenchApi, benchmark, measure::MeasureType};
910
use std::{
@@ -80,9 +81,11 @@ pub struct BenchmarkCommand {
8081
output_file: Option<String>,
8182

8283
/// The type of measurement to use (cycles, insts-retired, perf-counters, noop, vtune)
83-
/// when recording the benchmark performance.
84-
#[structopt(long, short, default_value = "cycles")]
85-
measure: MeasureType,
84+
/// when recording the benchmark performance. This option can be specified more than
85+
/// once if to record multiple measurements. If no measures are specified,
86+
/// the "cycles" measure will be used.
87+
#[structopt(long = "measure", short = "m", multiple = true)]
88+
measures: Vec<MeasureType>,
8689

8790
/// Pass this flag to only run benchmarks over "small" workloads (rather
8891
/// than the larger, default workloads).
@@ -194,7 +197,12 @@ impl BenchmarkCommand {
194197
let stdin = None;
195198

196199
let mut measurements = Measurements::new(this_arch(), engine, wasm_file);
197-
let mut measure = self.measure.build();
200+
let mut measure = if self.measures.len() <= 1 {
201+
let measure = self.measures.first().unwrap_or(&MeasureType::Cycles);
202+
measure.build()
203+
} else {
204+
Box::new(MultiMeasure::new(self.measures.iter().map(|m| m.build())))
205+
};
198206

199207
// Create the bench API engine and cache it for reuse across all
200208
// iterations of this benchmark.
@@ -354,8 +362,12 @@ impl BenchmarkCommand {
354362
.arg(self.iterations_per_process.to_string())
355363
.arg("--engine")
356364
.arg(&engine)
357-
.arg("--measure")
358-
.arg(self.measure.to_string())
365+
.args(
366+
self.measures
367+
.iter()
368+
.map(|m| ["--measure".to_string(), m.to_string()])
369+
.flatten(),
370+
)
359371
.arg("--raw")
360372
.arg("--output-format")
361373
// Always use JSON when privately communicating with a

crates/recorder/src/measure/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ pub mod counters;
8383
pub mod insts;
8484

8585
pub mod cycles;
86+
pub mod multi;
8687
pub mod noop;
88+
pub mod time;
8789
pub mod vtune;
8890

8991
/// [MeasureType] enumerates the implementations of [Measure] and allows us to `build` an instance
@@ -98,6 +100,9 @@ pub enum MeasureType {
98100
/// No measurement.
99101
Noop,
100102

103+
/// Simple time measurement.
104+
Time,
105+
101106
/// Measure cycles using, e.g., `RDTSC`.
102107
Cycles,
103108

@@ -117,6 +122,7 @@ impl fmt::Display for MeasureType {
117122
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118123
match self {
119124
MeasureType::Noop => write!(f, "noop"),
125+
MeasureType::Time => write!(f, "time"),
120126
MeasureType::Cycles => write!(f, "cycles"),
121127
MeasureType::VTune => write!(f, "vtune"),
122128
#[cfg(target_os = "linux")]
@@ -132,6 +138,7 @@ impl FromStr for MeasureType {
132138
fn from_str(s: &str) -> Result<Self, Self::Err> {
133139
match s {
134140
"noop" => Ok(Self::Noop),
141+
"time" => Ok(Self::Time),
135142
"cycles" => Ok(Self::Cycles),
136143
"vtune" => Ok(Self::VTune),
137144
#[cfg(target_os = "linux")]
@@ -150,6 +157,7 @@ impl MeasureType {
150157
pub fn build(&self) -> Box<dyn Measure> {
151158
match self {
152159
Self::Noop => Box::new(noop::NoopMeasure::new()),
160+
Self::Time => Box::new(time::TimeMeasure::new()),
153161
Self::Cycles => Box::new(cycles::CycleMeasure::new()),
154162
Self::VTune => Box::new(vtune::VTuneMeasure::new()),
155163
#[cfg(target_os = "linux")]
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//! Multiplexing measurement in order to take multiple measures
2+
//! from a single entry point.
3+
4+
use sightglass_data::Phase;
5+
6+
use super::{Measure, Measurements};
7+
8+
pub struct MultiMeasure {
9+
measures: Vec<Box<dyn Measure>>,
10+
}
11+
12+
impl MultiMeasure {
13+
pub fn new<I>(measures: I) -> Self
14+
where
15+
I: IntoIterator<Item = Box<dyn Measure>>,
16+
{
17+
Self {
18+
measures: measures.into_iter().collect(),
19+
}
20+
}
21+
}
22+
23+
impl Measure for MultiMeasure {
24+
fn start(&mut self, phase: Phase) {
25+
for measure in self.measures.iter_mut() {
26+
measure.start(phase)
27+
}
28+
}
29+
30+
fn end(&mut self, phase: Phase, measurements: &mut Measurements) {
31+
for measure in self.measures.iter_mut() {
32+
measure.end(phase, measurements)
33+
}
34+
}
35+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//! Measure time elapsed time for each benchmark; note that this is
2+
//! often probably not the best measurement as, unlike cycle counts,
3+
//! there are likely additional external factors that could skew
4+
//! results (such as cpu frequency scaling, etc.).
5+
6+
use std::time::Instant;
7+
8+
use super::{Measure, Measurements};
9+
use sightglass_data::Phase;
10+
11+
pub struct TimeMeasure(Option<Instant>);
12+
13+
impl TimeMeasure {
14+
pub fn new() -> Self {
15+
Self(None)
16+
}
17+
}
18+
19+
impl Measure for TimeMeasure {
20+
fn start(&mut self, _phase: Phase) {
21+
let start = Instant::now();
22+
self.0 = Some(start);
23+
}
24+
25+
fn end(&mut self, phase: Phase, measurements: &mut Measurements) {
26+
let elapsed = self.0.take().unwrap().elapsed();
27+
measurements.add(phase, "nanoseconds".into(), elapsed.as_nanos() as u64);
28+
}
29+
}

0 commit comments

Comments
 (0)