Skip to content

Commit 5ff7bd8

Browse files
authored
Merge pull request #281 from fitzgen/benchmark-particular-phases
Allow benchmarking just one particular compilation/instantiation/execution phase
2 parents e5003d5 + 51e1d7c commit 5ff7bd8

File tree

4 files changed

+199
-95
lines changed

4 files changed

+199
-95
lines changed

crates/cli/src/benchmark.rs

+81-37
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ use crate::suite::BenchmarkOrSuite;
22
use anyhow::{anyhow, Context, Result};
33
use rand::{rngs::SmallRng, Rng, SeedableRng};
44
use sightglass_data::{Format, Measurement, Phase};
5+
use sightglass_recorder::bench_api::Engine;
56
use sightglass_recorder::cpu_affinity::bind_to_single_core;
67
use sightglass_recorder::measure::Measurements;
7-
use sightglass_recorder::{bench_api::BenchApi, benchmark::benchmark, measure::MeasureType};
8+
use sightglass_recorder::{bench_api::BenchApi, benchmark, measure::MeasureType};
89
use std::{
910
fs,
1011
io::{self, BufWriter, Write},
@@ -105,9 +106,10 @@ pub struct BenchmarkCommand {
105106
#[structopt(short("d"), long("working-dir"), parse(from_os_str))]
106107
working_dir: Option<PathBuf>,
107108

108-
/// Stop measuring after the given phase (compilation/instantiation/execution).
109-
#[structopt(long("stop-after"))]
110-
stop_after_phase: Option<Phase>,
109+
/// Benchmark only the given phase (compilation, instantiation, or
110+
/// execution). Benchmarks all phases if omitted.
111+
#[structopt(long("benchmark-phase"))]
112+
benchmark_phase: Option<Phase>,
111113

112114
/// The significance level for confidence intervals. Typical values are 0.01
113115
/// and 0.05, which correspond to 99% and 95% confidence respectively. This
@@ -178,56 +180,98 @@ impl BenchmarkCommand {
178180
let bytes = fs::read(&wasm_file).context("Attempting to read Wasm bytes")?;
179181
log::debug!("Wasm benchmark size: {} bytes", bytes.len());
180182

183+
let wasm_hash = {
184+
use std::collections::hash_map::DefaultHasher;
185+
use std::hash::{Hash, Hasher};
186+
let mut hasher = DefaultHasher::new();
187+
wasm_file.hash(&mut hasher);
188+
hasher.finish()
189+
};
190+
let stdout = format!("stdout-{:x}-{}.log", wasm_hash, std::process::id());
191+
let stdout = Path::new(&stdout);
192+
let stderr = format!("stderr-{:x}-{}.log", wasm_hash, std::process::id());
193+
let stderr = Path::new(&stderr);
194+
let stdin = None;
195+
181196
let mut measurements = Measurements::new(this_arch(), engine, wasm_file);
182197
let mut measure = self.measure.build();
183198

199+
// Create the bench API engine and cache it for reuse across all
200+
// iterations of this benchmark.
201+
let engine = Engine::new(
202+
&mut bench_api,
203+
&working_dir,
204+
stdout,
205+
stderr,
206+
stdin,
207+
&mut measurements,
208+
&mut measure,
209+
self.engine_flags.as_deref(),
210+
);
211+
let mut engine = Some(engine);
212+
213+
// And if we are benchmarking just a post-compilation phase,
214+
// then eagerly compile the Wasm module for reuse.
215+
let mut module = None;
216+
if let Some(Phase::Instantiation | Phase::Execution) = self.benchmark_phase {
217+
module = Some(engine.take().unwrap().compile(&bytes));
218+
}
219+
184220
// Run the benchmark (compilation, instantiation, and execution) several times in
185221
// this process.
186-
for i in 0..self.iterations_per_process {
187-
let wasm_hash = {
188-
use std::collections::hash_map::DefaultHasher;
189-
use std::hash::{Hash, Hasher};
190-
let mut hasher = DefaultHasher::new();
191-
wasm_file.hash(&mut hasher);
192-
hasher.finish()
193-
};
194-
let stdout = format!("stdout-{:x}-{}-{}.log", wasm_hash, std::process::id(), i);
195-
let stdout = Path::new(&stdout);
196-
let stderr = format!("stderr-{:x}-{}-{}.log", wasm_hash, std::process::id(), i);
197-
let stderr = Path::new(&stderr);
198-
let stdin = None;
199-
200-
benchmark(
201-
&mut bench_api,
202-
&working_dir,
203-
stdout,
204-
stderr,
205-
stdin,
206-
&bytes,
207-
self.stop_after_phase.clone(),
208-
self.engine_flags.as_deref(),
209-
&mut measure,
210-
&mut measurements,
211-
)?;
222+
for _ in 0..self.iterations_per_process {
223+
match self.benchmark_phase {
224+
None => {
225+
let new_engine = benchmark::all(engine.take().unwrap(), &bytes)?;
226+
engine = Some(new_engine);
227+
}
228+
Some(Phase::Compilation) => {
229+
let new_engine =
230+
benchmark::compilation(engine.take().unwrap(), &bytes)?;
231+
engine = Some(new_engine);
232+
}
233+
Some(Phase::Instantiation) => {
234+
let new_module = benchmark::instantiation(module.take().unwrap())?;
235+
module = Some(new_module);
236+
}
237+
Some(Phase::Execution) => {
238+
let new_module = benchmark::execution(module.take().unwrap())?;
239+
module = Some(new_module);
240+
}
241+
}
212242

213243
self.check_output(Path::new(wasm_file), stdout, stderr)?;
214-
measurements.next_iteration();
244+
engine
245+
.as_mut()
246+
.map(|e| e.measurements())
247+
.or_else(|| module.as_mut().map(|m| m.measurements()))
248+
.unwrap()
249+
.next_iteration();
215250
}
216251

252+
drop((engine, module));
217253
all_measurements.extend(measurements.finish());
218254
}
219255
}
220256

257+
// If we are only benchmarking one phase then filter out any
258+
// measurements for other phases. These get included because we have to
259+
// compile at least once to measure instantiation, for example.
260+
if let Some(phase) = self.benchmark_phase {
261+
all_measurements.retain(|m| m.phase == phase);
262+
}
263+
221264
self.write_results(&all_measurements, &mut output_file)?;
222265
Ok(())
223266
}
224267

225268
/// Assert that our actual `stdout` and `stderr` match our expectations.
226269
fn check_output(&self, wasm_file: &Path, stdout: &Path, stderr: &Path) -> Result<()> {
227-
// If we aren't going through all phases and executing the Wasm, then we
228-
// won't have any actual output to check.
229-
if self.stop_after_phase.is_some() {
230-
return Ok(());
270+
match self.benchmark_phase {
271+
None | Some(Phase::Execution) => {}
272+
// If we aren't executing the Wasm, then we won't have any actual
273+
// output to check.
274+
Some(Phase::Compilation | Phase::Instantiation) => return Ok(()),
231275
}
232276

233277
let wasm_file_dir: PathBuf = if let Some(dir) = wasm_file.parent() {
@@ -326,8 +370,8 @@ impl BenchmarkCommand {
326370
command.env("WASM_BENCH_USE_SMALL_WORKLOAD", "1");
327371
}
328372

329-
if let Some(phase) = self.stop_after_phase {
330-
command.arg("--stop-after").arg(phase.to_string());
373+
if let Some(phase) = self.benchmark_phase {
374+
command.arg("--benchmark-phase").arg(phase.to_string());
331375
}
332376

333377
if let Some(flags) = &self.engine_flags {

crates/cli/tests/all/benchmark.rs

+26-4
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use sightglass_data::Measurement;
55
use std::path::PathBuf;
66

77
#[test]
8-
fn benchmark_stop_after_compilation() {
8+
fn benchmark_phase_compilation() {
99
sightglass_cli_benchmark()
1010
.arg("--raw")
1111
.arg("--processes")
1212
.arg("2")
1313
.arg("--iterations-per-process")
1414
.arg("1")
15-
.arg("--stop-after")
15+
.arg("--benchmark-phase")
1616
.arg("compilation")
1717
.arg(benchmark("noop"))
1818
.assert()
@@ -25,25 +25,47 @@ fn benchmark_stop_after_compilation() {
2525
}
2626

2727
#[test]
28-
fn benchmark_stop_after_instantiation() {
28+
fn benchmark_phase_instantiation() {
2929
sightglass_cli_benchmark()
3030
.arg("--raw")
3131
.arg("--processes")
3232
.arg("2")
3333
.arg("--iterations-per-process")
3434
.arg("1")
35-
.arg("--stop-after")
35+
.arg("--benchmark-phase")
3636
.arg("instantiation")
3737
.arg(benchmark("noop"))
3838
.assert()
3939
.success()
4040
.stdout(
4141
predicate::str::contains("Compilation")
42+
.not()
4243
.and(predicate::str::contains("Instantiation"))
4344
.and(predicate::str::contains("Execution").not()),
4445
);
4546
}
4647

48+
#[test]
49+
fn benchmark_phase_execution() {
50+
sightglass_cli_benchmark()
51+
.arg("--raw")
52+
.arg("--processes")
53+
.arg("2")
54+
.arg("--iterations-per-process")
55+
.arg("1")
56+
.arg("--benchmark-phase")
57+
.arg("execution")
58+
.arg(benchmark("noop"))
59+
.assert()
60+
.success()
61+
.stdout(
62+
predicate::str::contains("Compilation")
63+
.not()
64+
.and(predicate::str::contains("Instantiation").not())
65+
.and(predicate::str::contains("Execution")),
66+
);
67+
}
68+
4769
#[test]
4870
fn benchmark_json() {
4971
let assert = sightglass_cli_benchmark()

crates/recorder/src/bench_api.rs

+44-11
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,7 @@ pub struct Engine<'a, 'b, 'c, M> {
7474
engine: *mut c_void,
7575
}
7676

77-
impl<'a, 'b, 'c, M> Engine<'a, 'b, 'c, M>
78-
where
79-
M: Measure,
80-
{
77+
impl<'a, 'b, 'c, M> Engine<'a, 'b, 'c, M> {
8178
/// Construct a new engine from the given `BenchApi`.
8279
// NB: take a mutable reference to the `BenchApi` so that no one else can
8380
// call its API methods out of order.
@@ -90,7 +87,10 @@ where
9087
measurements: &'a mut Measurements<'c>,
9188
measure: &'a mut M,
9289
execution_flags: Option<&'a str>,
93-
) -> Self {
90+
) -> Self
91+
where
92+
M: Measure,
93+
{
9494
let working_dir = working_dir.display().to_string();
9595
let stdout_path = stdout_path.display().to_string();
9696
let stderr_path = stderr_path.display().to_string();
@@ -134,6 +134,11 @@ where
134134
}
135135
}
136136

137+
/// Get this engine's measurements.
138+
pub fn measurements(&mut self) -> &mut Measurements<'c> {
139+
unsafe { (*self.measurement_data).get_mut().1 }
140+
}
141+
137142
/// Compile the Wasm into a module.
138143
pub fn compile(self, wasm: &[u8]) -> Module<'a, 'b, 'c, M> {
139144
let result =
@@ -143,31 +148,43 @@ where
143148
}
144149

145150
/// Bench API callback for the start of compilation.
146-
extern "C" fn compilation_start(data: *mut u8) {
151+
extern "C" fn compilation_start(data: *mut u8)
152+
where
153+
M: Measure,
154+
{
147155
log::debug!("Starting compilation measurement");
148156
let data = data as *mut (*mut M, *mut Measurements<'b>);
149157
let measure = unsafe { data.as_mut().unwrap().0.as_mut().unwrap() };
150158
measure.start(Phase::Compilation);
151159
}
152160

153161
/// Bench API callback for the start of instantiation.
154-
extern "C" fn instantiation_start(data: *mut u8) {
162+
extern "C" fn instantiation_start(data: *mut u8)
163+
where
164+
M: Measure,
165+
{
155166
log::debug!("Starting instantiation measurement");
156167
let data = data as *mut (*mut M, *mut Measurements<'b>);
157168
let measure = unsafe { data.as_mut().unwrap().0.as_mut().unwrap() };
158169
measure.start(Phase::Instantiation);
159170
}
160171

161172
/// Bench API callback for the start of execution.
162-
extern "C" fn execution_start(data: *mut u8) {
173+
extern "C" fn execution_start(data: *mut u8)
174+
where
175+
M: Measure,
176+
{
163177
log::debug!("Starting execution measurement");
164178
let data = data as *mut (*mut M, *mut Measurements<'b>);
165179
let measure = unsafe { data.as_mut().unwrap().0.as_mut().unwrap() };
166180
measure.start(Phase::Execution);
167181
}
168182

169183
/// Bench API callback for the end of compilation.
170-
extern "C" fn compilation_end(data: *mut u8) {
184+
extern "C" fn compilation_end(data: *mut u8)
185+
where
186+
M: Measure,
187+
{
171188
let data = data as *mut (*mut M, *mut Measurements<'b>);
172189
let (measure, measurements) = unsafe {
173190
let data = data.as_mut().unwrap();
@@ -178,7 +195,10 @@ where
178195
}
179196

180197
/// Bench API callback for the end of instantiation.
181-
extern "C" fn instantiation_end(data: *mut u8) {
198+
extern "C" fn instantiation_end(data: *mut u8)
199+
where
200+
M: Measure,
201+
{
182202
let data = data as *mut (*mut M, *mut Measurements<'b>);
183203
let (measure, measurements) = unsafe {
184204
let data = data.as_mut().unwrap();
@@ -189,7 +209,10 @@ where
189209
}
190210

191211
/// Bench API callback for the end of execution.
192-
extern "C" fn execution_end(data: *mut u8) {
212+
extern "C" fn execution_end(data: *mut u8)
213+
where
214+
M: Measure,
215+
{
193216
let data = data as *mut (*mut M, *mut Measurements<'b>);
194217
let (measure, measurements) = unsafe {
195218
let data = data.as_mut().unwrap();
@@ -215,6 +238,16 @@ pub struct Module<'a, 'b, 'c, M> {
215238
}
216239

217240
impl<'a, 'b, 'c, M> Module<'a, 'b, 'c, M> {
241+
/// Turn this module back into an engine.
242+
pub fn into_engine(self) -> Engine<'a, 'b, 'c, M> {
243+
self.engine
244+
}
245+
246+
/// Get this engine's measurements.
247+
pub fn measurements(&mut self) -> &mut Measurements<'c> {
248+
self.engine.measurements()
249+
}
250+
218251
/// Instantiate this module, returning the resulting `Instance`.
219252
pub fn instantiate(self) -> Instance<'a, 'b, 'c, M> {
220253
let result = unsafe { (self.engine.bench_api.wasm_bench_instantiate)(self.engine.engine) };

0 commit comments

Comments
 (0)