diff --git a/benches/barbershop.rs b/benches/barbershop.rs index ff9d392197b64d86080e5a249cd260dcf3abf645..ce7f58c374d3b9ffc38c7f5dfd90903031f13569 100644 --- a/benches/barbershop.rs +++ b/benches/barbershop.rs @@ -1,13 +1,16 @@ -use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main, BatchSize, PlotConfiguration, AxisScale, SamplingMode}; -use std::{process::Command, time::Duration}; -use itertools::Itertools; +use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main, + BatchSize, PlotConfiguration, AxisScale, SamplingMode}; -const RANGE : u32 = 10; -const STEP : Time = 1000.0; +mod support; + +const SLX_PATH: &'static str = "C:/Wolverine/SLX/sse.exe"; +const RANGE: u32 = 10; +const STEP: Time = 16000.0; fn barbershop_bench(c: &mut Criterion) { let mut group = c.benchmark_group("Barbershop"); + // set-up the benchmark parameters group.confidence_level(0.99); group.plot_config( PlotConfiguration::default() @@ -15,8 +18,8 @@ fn barbershop_bench(c: &mut Criterion) { ); group.sampling_mode(SamplingMode::Linear); + // vary in the length of the simulation run for sim_duration in (0..RANGE).map(|c| Time::from(1 << c)*STEP) { - // benchmark the SLX implementation let duration = sim_duration.to_string(); let args = [ "/silent", @@ -29,43 +32,19 @@ fn barbershop_bench(c: &mut Criterion) { duration.as_str() ]; - group.measurement_time(Duration::from_secs(60)); + // benchmark the SLX implementation group.bench_function( BenchmarkId::new("SLX", sim_duration), - |b| b.iter_custom(|iters| { - (0..iters) - .into_iter() - .chunks(3200000/(sim_duration as usize)) - .into_iter() - .map(|range| { - let rc = Command::new("C:/Wolverine/SLX/sse.exe") - .args(args.iter()) - .arg(range.count().to_string()) - .output() - .expect("failed to run the SLX program"); - - if !rc.status.success() { - panic!(match rc.status.code().unwrap() { - -10001 => "Unable to find a security key with SLX permission", - -10002 => "The command line includes an invalid option", - -10003 => "The source file cannot be found", - -10004 => "The specified/implied output file cannot be written", - -10005 => "The program contains compile-time errors", - -10006 => "The program has terminated with a run-time error", - -10007 => "The /genrts option failed", - _ => "Unknown error code" - }) - } - - Duration::from_secs_f64( - (&String::from_utf8_lossy(&rc.stdout)).trim().parse().unwrap() - ) - }).sum() - }) + |b| b.iter_custom(|iters| + support::slx_bench( + SLX_PATH, + &args, + iters as usize + ).expect("couldn't benchmark the SLX program") + ) ); // benchmark the Rust implementation - group.measurement_time(Duration::from_secs(5)); group.bench_function( BenchmarkId::new("Rust", sim_duration), |b| b.iter_batched( diff --git a/benches/ferry.rs b/benches/ferry.rs index b5667915873bd41d3f5dd2bcef3d055a3be600c4..c7c46c60b9d28809139361470e17b95a1bf2db96 100644 --- a/benches/ferry.rs +++ b/benches/ferry.rs @@ -1,14 +1,17 @@ -use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main, BatchSize, PlotConfiguration, AxisScale, SamplingMode}; -use std::{process::Command, time::Duration}; -use itertools::Itertools; +use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main, + BatchSize, PlotConfiguration, AxisScale, SamplingMode}; -const SEED : u64 = 100000; -const RANGE : u32 = 10; -const STEP : Time = 1000.0; +mod support; + +const SLX_PATH: &'static str = "C:/Wolverine/SLX/sse.exe"; +const SEED: u64 = 100000; +const RANGE: u32 = 10; +const STEP: Time = 1000.0; fn ferry_bench(c: &mut Criterion) { let mut group = c.benchmark_group("Ferry"); + // set-up the benchmark parameters group.confidence_level(0.99); group.plot_config( PlotConfiguration::default() @@ -16,8 +19,8 @@ fn ferry_bench(c: &mut Criterion) { ); group.sampling_mode(SamplingMode::Linear); + // vary in the length of the simulation run for (i, sim_duration) in (0..RANGE).map(|c| Time::from(1 << c)*STEP).enumerate() { - // benchmark the SLX implementation let duration = sim_duration.to_string(); let args = [ "/silent", @@ -30,43 +33,19 @@ fn ferry_bench(c: &mut Criterion) { duration.as_str() ]; - group.measurement_time(Duration::from_secs(60)); + // benchmark the SLX implementation group.bench_function( BenchmarkId::new("SLX", sim_duration), - |b| b.iter_custom(|iters| { - (0..iters) - .into_iter() - .chunks(620000/(sim_duration as usize)) - .into_iter() - .map(|range| { - let rc = Command::new("C:/Wolverine/SLX/sse.exe") - .args(args.iter()) - .arg(range.count().to_string()) - .output() - .expect("failed to run the SLX program"); - - if !rc.status.success() { - panic!(match rc.status.code().unwrap() { - -10001 => "Unable to find a security key with SLX permission", - -10002 => "The command line includes an invalid option", - -10003 => "The source file cannot be found", - -10004 => "The specified/implied output file cannot be written", - -10005 => "The program contains compile-time errors", - -10006 => "The program has terminated with a run-time error", - -10007 => "The /genrts option failed", - _ => "Unknown error code" - }) - } - - Duration::from_secs_f64( - (&String::from_utf8_lossy(&rc.stdout)).trim().parse().unwrap() - ) - }).sum() - }) + |b| b.iter_custom(|iters| + support::slx_bench( + SLX_PATH, + &args, + iters as usize + ).expect("couldn't benchmark the SLX program") + ) ); // benchmark the Rust implementation - group.measurement_time(Duration::from_secs(5)); group.bench_function( BenchmarkId::new("Rust", sim_duration), |b| b.iter_batched( diff --git a/benches/philosophers.rs b/benches/philosophers.rs index 654cc1b60893dd2ab6a6bd59e1dfaa8f6b80adbf..cb4aae7832579dc0c3c98274cea4f08a190c2707 100644 --- a/benches/philosophers.rs +++ b/benches/philosophers.rs @@ -1,15 +1,18 @@ -use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main, BatchSize, PlotConfiguration, AxisScale, SamplingMode}; -use std::{process::Command, time::Duration}; -use itertools::Itertools; +use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main, + PlotConfiguration, AxisScale, SamplingMode}; -const SEED : u64 = 100000; -const RANGE : u32 = 10; -const STEP : usize = 10; +mod support; + +const SLX_PATH: &'static str = "C:/Wolverine/SLX/sse.exe"; +const SEED: u64 = 100000; +const RANGE: u32 = 10; +const STEP: u64 = 10; const PHILOSOPHER_COUNT: usize = 5; fn philosopher_bench(c: &mut Criterion) { let mut group = c.benchmark_group("Philosophers"); + // benchmark the SLX implementation group.confidence_level(0.99); group.plot_config( PlotConfiguration::default() @@ -17,8 +20,8 @@ fn philosopher_bench(c: &mut Criterion) { ); group.sampling_mode(SamplingMode::Linear); - for (i, experiment_count) in (0..RANGE).map(|c| (1 << c)*STEP).enumerate() { - // benchmark the SLX implementation + // vary in the number of performed experiments + for experiment_count in (0..RANGE).map(|c| (1 << c)*STEP) { let count = experiment_count.to_string(); let args = [ "/silent", @@ -31,46 +34,28 @@ fn philosopher_bench(c: &mut Criterion) { count.as_str() ]; - group.measurement_time(Duration::from_secs(60)); + // benchmark the SLX implementation group.bench_function( BenchmarkId::new("SLX", experiment_count), - |b| b.iter_custom(|iters| { - let rc = Command::new("C:/Wolverine/SLX/sse.exe") - .args(args.iter()) - .arg(iters.to_string()) - .output() - .expect("failed to run the SLX program"); - - if !rc.status.success() { - panic!(match rc.status.code().unwrap() { - -10001 => "Unable to find a security key with SLX permission", - -10002 => "The command line includes an invalid option", - -10003 => "The source file cannot be found", - -10004 => "The specified/implied output file cannot be written", - -10005 => "The program contains compile-time errors", - -10006 => "The program has terminated with a run-time error", - -10007 => "The /genrts option failed", - _ => "Unknown error code" - }) - } - - Duration::from_secs_f64( - (&String::from_utf8_lossy(&rc.stdout)).trim().parse().unwrap() - ) - }) + |b| b.iter_custom(|iters| + support::slx_bench( + SLX_PATH, + &args, + iters as usize + ).expect("couldn't benchmark the SLX program") + ) ); // benchmark the Rust implementation - group.measurement_time(Duration::from_secs(5)); group.bench_function( BenchmarkId::new("Rust", experiment_count), |b| b.iter(|| (1..=experiment_count) .into_par_iter() - .map(|j| + .map(|experiment_no| simulation( // global data Shared { - master_rng: RefCell::new(SmallRng::seed_from_u64(SEED*(((i+1)*j) as u64))), + master_rng: RefCell::new(SmallRng::seed_from_u64(SEED*experiment_no)), sim_duration: Cell::default() }, // simulation entry point diff --git a/benches/support/mod.rs b/benches/support/mod.rs new file mode 100644 index 0000000000000000000000000000000000000000..f38c7cd1ad9006fe54c2a3f687f526c62dd67e05 --- /dev/null +++ b/benches/support/mod.rs @@ -0,0 +1,79 @@ +use std::{process::Command, time::Duration, iter::once}; +use itertools::Itertools; + +/// Runs the SLX runtime environment once for an SLX program that measures its +/// own duration and returns it on the standard output. +/// +/// Errors during this execution are returned back to the caller. +fn slx_run_once(program: &str, arguments: &[&str], iterations: usize) -> Result<Duration, (i32, String)> { + // start the SLX runtime and wait for its return + let rc = Command::new(program) + .args(arguments) + .arg(iterations.to_string()) + .output() + .map_err(|err| (-1, format!("Failed to run the SLX program: {}", err)))?; + + if !rc.status.success() { + // check the error code and add a description to it + Err(rc.status + .code() + .map_or_else( + || (-10000, "Unknown error code".to_string()), + |code| { + (code, match code { + -10001 => "Unable to find a security key with SLX permission".to_string(), + -10002 => "The command line includes an invalid option".to_string(), + -10003 => "The source file cannot be found".to_string(), + -10004 => "The specified/implied output file cannot be written".to_string(), + -10005 => "The program contains compile-time errors".to_string(), + -10006 => "The program has terminated with a run-time error".to_string(), + -10007 => "The /genrts option failed".to_string(), + _ => format!("Unknown error code: {}", code) + }) + } + ) + ) + } else { + Ok(Duration::from_secs_f64( + (&String::from_utf8_lossy(&rc.stdout)).trim().parse().unwrap() + )) + } +} + +/// Repeats runs of an SLX program for a specified number of times and collects +/// the total (real-time) duration of those combined runs. +/// +/// The SLX program in question has to report its own real-time duration using +/// the standard output. +pub fn slx_bench(program: &str, arguments: &[&str], iterations: usize) -> Result<Duration, String> { + // try to complete the iterations in a single run of the SLX program + slx_run_once(program, arguments, iterations) + .or_else(|(code, desc)| { + if code == -10006 { + // Runtime error: this happens when the free version of SLX + // exceeds its total instance budget, either through a + // complicated scenario or too many iterations in one call. + // We can still make this work by running SLX multiple times + // with lower iteration counts and combining the timings. + // This effectively trades benchmark speed with simulation model + // size. + (1..) + .map(|i| iterations >> i) + .take_while(|&chunk_size| chunk_size > 0) + // .inspect(|chunk_size| println!("reducing the chunk size to {}", chunk_size)) + .map(|chunk_size| { + (0..(iterations - 1)/chunk_size) + .map(|_| chunk_size) + .chain(once((iterations - 1) % chunk_size + 1)) + .map(|size| + slx_run_once(program, arguments, size) + ) + .fold_ok(Duration::default(), |duration, run| duration + run) + }) + .find_map(|result| result.ok()) + .ok_or(desc) + } else { + Err(desc) + } + }) +} diff --git a/slx/barbershop.slx b/slx/barbershop.slx index 0b330030c6864aecc017408163fa8ef22101c52a..50aa666ae6a0a5018fc1c960d965ef3687d5ba39 100644 --- a/slx/barbershop.slx +++ b/slx/barbershop.slx @@ -1,7 +1,7 @@ import <h7> // enable this macro for the benchmark -#define BENCH +// #define BENCH module barbershop { // globally shared data diff --git a/src/main.rs b/src/main.rs index ba03b4f27f12cbd0a272db7feda19f6cf909d4f9..d3ab051e23915939733a8424b28545c664e5940d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,30 +1,12 @@ -use simcore_rs::{channel, simulation}; fn main() { - simulation( - (), - |sim| sim.process(async move { - println!("[{}] Begin", sim.now()); - - let (sx, rx) = channel(); - sim.activate(async move { - for i in 0.. { - sim.advance(1.0).await; - sx.send(i).await.ok(); - } - }); - - sim.select(async move { - while let Some(i) = rx.recv().await { - println!("[{}] Received {}", sim.now(), i); - if i >= 3 { break; } - } - - println!("[{}] Exit Receiver Process", sim.now()); - }, sim.advance(5.0)).await; - - sim.advance(10.0).await; - println!("[{}] End", sim.now()); - }) - ); + let iterations = 11; + let chunk_size = 4; + + let res: Vec<_> = (0..(iterations - 1)/chunk_size) + .map(|_| chunk_size) + .chain(std::iter::once((iterations - 1) % chunk_size + 1)) + .collect(); + + println!("{:?}", res); }