use clap::Parser;
use std::process::{Command, ExitStatus, Stdio};
const STDOUT: &str = "out.chain";
const SORTING: [&str; 2] = [
"target/release/chaintools sort -c {chain} -o out.chain --sort-by score --threads 16",
"./bench/chainSort {chain} out.chain",
];
const FILTER: [&str; 2] = [
"target/release/chaintools filter -c {chain} -o out.chain --min-score 15000 --min-target-start 12400 --threads 16",
"./bench/chainFilter {chain} -minScore=15000 -tStartMin=12400 > out.chain",
];
const MERGE_SORT: [&str; 2] = [
"target/release/chaintools split -c {chain} --files 10 -o . && target/release/chaintools merge -c chains/* -o out.chain --threads 16 -S score && rm -rf chains",
"target/release/chaintools split -c {chain} --files 10 -o . && ./bench/chainMergeSort chains/* > out.chain && rm -rf chains",
];
#[derive(Debug, Parser)]
pub struct Args {
#[clap(short = 'c', long = "chain", help = "Path to chain file")]
chain: String,
#[clap(short = 'a',
value_delimiter = ',',
num_args = 1..,
help = "Extra arguments to pass to hyperfine"
)]
hyperfine_args: Vec<String>,
}
pub struct HyperfineCall {
pub warmup: u32,
pub min_runs: u32,
pub max_runs: Option<u32>,
pub export_csv: Option<String>,
pub export_markdown: Option<String>,
pub parameters: Vec<(String, Vec<String>)>,
pub setup: Option<String>,
pub cleanup: Option<String>,
pub commands: Vec<String>,
pub extras: Vec<String>,
}
impl Default for HyperfineCall {
fn default() -> Self {
Self {
warmup: 3,
min_runs: 5,
max_runs: None,
export_csv: None,
export_markdown: None,
parameters: Vec::new(),
setup: None,
cleanup: None,
commands: Vec::new(),
extras: Vec::new(),
}
}
}
impl HyperfineCall {
pub fn invoke(&self) -> ExitStatus {
let mut command = Command::new("hyperfine");
command
.stdout(Stdio::inherit())
.stderr(Stdio::inherit())
.stdin(Stdio::null());
command.arg("--warmup").arg(self.warmup.to_string());
command.arg("--min-runs").arg(self.min_runs.to_string());
if let Some(export_csv) = &self.export_csv {
command.arg("--export-csv").arg(export_csv);
}
if let Some(export_markdown) = &self.export_markdown {
command.arg("--export-markdown").arg(export_markdown);
}
for (flag, values) in &self.parameters {
command.arg("-L").arg(flag).arg(values.join(","));
}
if let Some(setup) = &self.setup {
command.arg("--setup").arg(setup);
}
if let Some(cleanup) = &self.cleanup {
command.arg("--cleanup").arg(cleanup);
}
if let Some(max_runs) = self.max_runs {
command.arg("--max-runs").arg(max_runs.to_string());
}
if !self.extras.is_empty() {
command.args(&self.extras);
}
for cmd in &self.commands {
command.arg(cmd);
}
command.status().expect("Failed to run hyperfine")
}
}
fn benchmark() {
let args = Args::parse();
std::fs::create_dir_all("runs").unwrap_or_else(|e| panic!("{}", e));
let triplets = vec![
(&args.chain, SORTING.to_vec(), "sorting"),
(&args.chain, FILTER.to_vec(), "filter"),
(&args.chain, MERGE_SORT.to_vec(), "merge_sort"),
];
for (chain, tools, run_name) in triplets {
let csv = format!("bench_{}.csv", run_name);
let md = format!("bench_{}.md", run_name);
#[allow(clippy::needless_update)]
let code = HyperfineCall {
warmup: 3,
min_runs: 3,
max_runs: Some(10),
export_csv: Some(format!("runs/{}", csv).to_string()),
export_markdown: Some(format!("runs/{}", md).to_string()),
parameters: vec![("chain".to_string(), vec![chain.to_string()])],
setup: Some("cargo build --release --all-features".to_string()),
cleanup: Some(format!("rm -rf output {} chains", STDOUT)),
commands: tools
.iter()
.map(|cmd| cmd.to_string())
.collect::<Vec<String>>(),
extras: args
.hyperfine_args
.iter()
.map(|s| format!("--{}", s))
.collect(),
..Default::default()
}
.invoke()
.code()
.expect("Benchmark terminated unexpectedly");
if code != 0 {
eprintln!("Benchmark failed with exit code {}", code);
}
}
}
fn main() {
benchmark();
}