use std::env;
use std::hint::black_box;
use std::process;
use std::time::Instant;
use elli_gf2::{
Strategy, avg_col_weight, generate_banded, generate_er_sparse, generate_ldpc_like,
recommended_strategy, reduce_with_strategy,
};
fn usage() -> ! {
eprintln!(
"Usage:
cargo run --release --bin bench_cli -- --family er_sparse --rows 2048 --cols 2048 --density 0.01 --strategy auto
cargo run --release --bin bench_cli -- --family ldpc_like --rows 2048 --cols 4096 --col-weight 6 --strategy auto
cargo run --release --bin bench_cli -- --family banded --rows 2048 --cols 2048 --bandwidth 64 --weight 10 --strategy auto
cargo run --release --bin bench_cli -- --family er_sparse --rows 2048 --cols 2048 --density 0.01 --compare-all
Options:
--family <er_sparse|ldpc_like|banded>
--rows <usize>
--cols <usize>
--density <f64> (required for er_sparse)
--col-weight <usize> (required for ldpc_like)
--bandwidth <usize> (required for banded)
--weight <usize> (required for banded)
--seed <u64> (default: 1337)
--strategy <sparse|packed|dense|auto> (default: auto)
--reps <usize> (default: 3)
--compare-all (ignore --strategy and print all backends)"
);
process::exit(1);
}
fn parse_strategy(s: &str) -> Strategy {
match s {
"sparse" => Strategy::SparseList,
"packed" => Strategy::PackedCached,
"dense" => Strategy::DenseMacro,
"auto" => Strategy::Auto,
_ => {
eprintln!("unknown strategy: {s}");
usage();
}
}
}
fn median_u128(xs: &mut [u128]) -> u128 {
xs.sort_unstable();
let n = xs.len();
if n % 2 == 0 {
(xs[n / 2 - 1] + xs[n / 2]) / 2
} else {
xs[n / 2]
}
}
fn time_strategy(rows: usize, cols: &[Vec<u32>], strategy: Strategy, reps: usize) -> (u128, usize) {
let mut samples = Vec::with_capacity(reps);
let rank_ref = reduce_with_strategy(rows, cols, strategy).rank;
for _ in 0..reps {
let t0 = Instant::now();
let out = black_box(reduce_with_strategy(rows, cols, strategy));
samples.push(t0.elapsed().as_micros());
black_box(out);
}
(median_u128(&mut samples), rank_ref)
}
fn main() {
let mut family: Option<String> = None;
let mut rows: Option<usize> = None;
let mut cols_n: Option<usize> = None;
let mut density: Option<f64> = None;
let mut col_weight: Option<usize> = None;
let mut bandwidth: Option<usize> = None;
let mut weight: Option<usize> = None;
let mut seed: u64 = 1337;
let mut reps: usize = 3;
let mut strategy = Strategy::Auto;
let mut compare_all = false;
let mut args = env::args().skip(1).peekable();
while let Some(arg) = args.next() {
match arg.as_str() {
"--family" => family = Some(args.next().unwrap_or_else(|| usage())),
"--rows" => {
rows = Some(
args.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage()),
)
}
"--cols" => {
cols_n = Some(
args.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage()),
)
}
"--density" => {
density = Some(
args.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage()),
)
}
"--col-weight" => {
col_weight = Some(
args.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage()),
)
}
"--bandwidth" => {
bandwidth = Some(
args.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage()),
)
}
"--weight" => {
weight = Some(
args.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage()),
)
}
"--seed" => {
seed = args
.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage())
}
"--reps" => {
reps = args
.next()
.unwrap_or_else(|| usage())
.parse()
.unwrap_or_else(|_| usage())
}
"--strategy" => strategy = parse_strategy(&args.next().unwrap_or_else(|| usage())),
"--compare-all" => compare_all = true,
_ => {
eprintln!("unknown arg: {arg}");
usage();
}
}
}
let family = family.unwrap_or_else(|| usage());
let rows = rows.unwrap_or_else(|| usage());
let cols_n = cols_n.unwrap_or_else(|| usage());
let cols = match family.as_str() {
"er_sparse" => generate_er_sparse(rows, cols_n, density.unwrap_or_else(|| usage()), seed),
"ldpc_like" => {
generate_ldpc_like(rows, cols_n, col_weight.unwrap_or_else(|| usage()), seed)
}
"banded" => generate_banded(
rows,
cols_n,
bandwidth.unwrap_or_else(|| usage()),
weight.unwrap_or_else(|| usage()),
seed,
),
_ => {
eprintln!("unknown family: {family}");
usage();
}
};
let avg_w = avg_col_weight(&cols);
let auto_choice = recommended_strategy(&cols);
let auto_rank = reduce_with_strategy(rows, &cols, Strategy::Auto).rank;
println!("family,rows,cols,avg_col_weight,requested_strategy,auto_strategy,elapsed_us,rank");
if compare_all {
let order = [
Strategy::SparseList,
Strategy::PackedCached,
Strategy::DenseMacro,
Strategy::Auto,
];
for strat in order {
let (elapsed_us, rank) = time_strategy(rows, &cols, strat, reps);
println!(
"{},{},{},{:.3},{:?},{:?},{},{}",
family, rows, cols_n, avg_w, strat, auto_choice, elapsed_us, rank
);
if rank != auto_rank {
eprintln!(
"rank mismatch under strategy {:?}: got {}, expected {}",
strat, rank, auto_rank
);
process::exit(2);
}
}
} else {
let (elapsed_us, rank) = time_strategy(rows, &cols, strategy, reps);
println!(
"{},{},{},{:.3},{:?},{:?},{},{}",
family, rows, cols_n, avg_w, strategy, auto_choice, elapsed_us, rank
);
if rank != auto_rank {
eprintln!(
"rank mismatch under strategy {:?}: got {}, expected {}",
strategy, rank, auto_rank
);
process::exit(2);
}
}
}