Skip to content
Snippets Groups Projects
Commit 7c9ebb55 authored by Dorian Weber's avatar Dorian Weber
Browse files

Ran some of the benchmarks and combined them with the examples in order to reduce redundancies.

parent 53fe48bf
Branches
No related merge requests found
......@@ -56,6 +56,15 @@ dependencies = [
"rustc_version",
]
[[package]]
name = "cc"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
......@@ -245,6 +254,15 @@ version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "jobserver"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.42"
......@@ -550,6 +568,7 @@ dependencies = [
name = "simcore-rs"
version = "0.1.0"
dependencies = [
"cc",
"criterion",
"itertools 0.10.0",
"rand",
......
......@@ -20,14 +20,20 @@ rayon = "1.5"
criterion = { version = "0.3.4", features = ["html_reports"]}
itertools = "0.10"
[[bench]]
[build-dependencies]
cc = { version = "1.0", features = ["parallel"] }
[[example]]
name = "barbershop"
bench = true
harness = false
[[bench]]
[[example]]
name = "ferry"
bench = true
harness = false
[[bench]]
[[example]]
name = "philosophers"
bench = true
harness = false
use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main,
BatchSize, PlotConfiguration, AxisScale, SamplingMode};
mod support;
const SLX_PATH: &'static str = "C:/Wolverine/SLX/sse.exe";
const RANGE: u32 = 10;
const STEP: Time = 1000.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()
.summary_scale(AxisScale::Logarithmic)
);
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) {
let duration = sim_duration.to_string();
let args = [
"/silent",
"/stdout",
"/noicon",
"/nowarn",
"/noxwarn",
"/#BENCH",
"slx\\barbershop.slx",
duration.as_str()
];
// benchmark the SLX implementation
group.bench_function(
BenchmarkId::new("SLX", sim_duration),
|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.bench_function(
BenchmarkId::new("Rust", sim_duration),
|b| b.iter_batched(
|| Shared { joe: Facility::new(), wait_time: RandomVar::new() },
|shared| simulation(
shared,
|sim| sim.process(barbershop(sim, sim_duration))
),
BatchSize::SmallInput
)
);
}
group.finish();
}
criterion_group!(benches, barbershop_bench);
criterion_main!(benches);
/* *************************** Barbershop Example *************************** */
use simcore_rs::{Time, SimContext, Facility, RandomVar, simulation};
use rand::{distributions::Uniform, rngs::SmallRng, SeedableRng, Rng};
/// Globally shared data.
struct Shared {
joe: Facility,
wait_time: RandomVar
}
// helper constants
const SEED_A : u64 = 100000;
const SEED_S : u64 = 200000;
/// Customer process with access to the barber and a random processing delay.
struct Customer { delay: Time }
impl Customer {
pub async fn actions(self, sim: SimContext<'_,Shared>) {
// access the barber and record the time for the report
let arrival_time = sim.now();
sim.shared().joe.seize().await;
sim.shared().wait_time.tabulate(sim.now() - arrival_time);
// spend time
sim.advance(self.delay).await;
// release the barber
sim.shared().joe.release();
}
}
async fn barbershop(sim: SimContext<'_,Shared>, duration: Time) {
// pseudo random number generators referenced from within the main process
let mut rng_a = SmallRng::seed_from_u64(SEED_A);
let mut rng_s = SmallRng::seed_from_u64(SEED_S);
// activate a process to generate the customers
sim.activate(async move {
let dist_a = Uniform::new(12.0, 24.0);
let dist_s = Uniform::new(12.0, 18.0);
loop {
sim.advance(rng_a.sample(dist_a)).await;
if sim.now() >= duration { return; }
sim.activate(Customer {
delay: rng_s.sample(dist_s)
}.actions(sim));
}
});
// wait until the store closes
sim.advance(duration).await;
// finish processing the queue (no more customers arrive)
sim.shared().joe.seize().await;
}
use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main,
BatchSize, PlotConfiguration, AxisScale, SamplingMode};
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()
.summary_scale(AxisScale::Logarithmic)
);
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() {
let duration = sim_duration.to_string();
let args = [
"/silent",
"/stdout",
"/noicon",
"/nowarn",
"/noxwarn",
"/#BENCH",
"slx\\ferry.slx",
duration.as_str()
];
// benchmark the SLX implementation
group.bench_function(
BenchmarkId::new("SLX", sim_duration),
|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.bench_function(
BenchmarkId::new("Rust", sim_duration),
|b| b.iter_batched(
|| Shared {
master_rng: RefCell::new(SmallRng::seed_from_u64(SEED*((i+1) as u64))),
ferry_cargo_len: RandomVar::new(),
ferry_load_time: RandomVar::new(),
car_wait_time: RandomVar::new(),
},
|shared| simulation(
shared,
|sim| sim.process(ferry(sim, sim_duration, 2, 4))
),
BatchSize::SmallInput
)
);
}
group.finish();
}
criterion_group!(benches, ferry_bench);
criterion_main!(benches);
/* ****************************** Ferry Example ***************************** */
use simcore_rs::{Time, SimContext, Sender, Receiver, RandomVar, channel, simulation};
use rand::{rngs::SmallRng, SeedableRng, Rng};
use rand_distr::{Exp, Normal, Distribution};
use std::cell::RefCell;
const HARBOR_DISTANCE: Time = 10.0;
const FERRY_TIMEOUT : Time = 5.0;
const FERRY_CAPACITY : usize = 5;
struct Shared {
master_rng: RefCell<SmallRng>,
ferry_cargo_len: RandomVar,
ferry_load_time: RandomVar,
car_wait_time: RandomVar,
}
#[derive(Debug)]
struct Car {
arrival_time: Time,
load_duration: Time
}
struct Pier {
rng: SmallRng,
landing_site: Sender<Car>
}
struct Ferry {
cargo: Vec<Car>,
timeout: Time,
travel_time: Time,
piers: Vec<Receiver<Car>>
}
impl Pier {
async fn actions(mut self, sim: SimContext<'_,Shared>) {
let arrival_delay = Exp::new(0.1).unwrap();
let loading_delay = Normal::new(0.5, 0.2).unwrap();
loop {
sim.advance(arrival_delay.sample(&mut self.rng)).await;
self.landing_site.send(Car {
arrival_time: sim.now(),
load_duration: loading_delay
.sample_iter(&mut self.rng)
.find(|&val| val >= 0.0)
.unwrap()
}).await.expect("no ferries in the simulation");
}
}
}
impl Ferry {
async fn actions(mut self, sim: SimContext<'_,Shared>) {
loop {
for pier in self.piers.iter() {
// unload the cars
for car in self.cargo.drain(..) {
sim.advance(car.load_duration).await;
}
let begin_loading = sim.now();
// wait until new cars arrive or a timeout occurs
while self.cargo.len() < self.cargo.capacity() {
if let Some(car) = sim.select(
pier.recv(),
async { sim.advance(self.timeout).await; None }
).await {
// a car arrived in time
sim.shared().car_wait_time.tabulate(sim.now() - car.arrival_time);
sim.advance(car.load_duration).await;
self.cargo.push(car);
} else {
// the timeout has been triggered
break;
}
}
sim.shared().ferry_load_time.tabulate(sim.now() - begin_loading);
sim.shared().ferry_cargo_len.tabulate(self.cargo.len() as f64);
// travel to the next harbor
sim.advance(self.travel_time).await;
}
}
}
}
async fn ferry(sim: SimContext<'_,Shared>, duration: Time, ferries: usize, harbors: usize) {
let mut ports = Vec::with_capacity(harbors);
// create all of the harbors
for _ in 0..harbors {
let (sx, rx) = channel();
let harbor = Pier {
rng: SmallRng::from_seed(sim.shared().master_rng.borrow_mut().gen()),
landing_site: sx
};
sim.activate(harbor.actions(sim));
ports.push(rx);
}
// create all of the ferries
for i in 0..ferries {
let ferry = Ferry {
cargo: Vec::with_capacity(FERRY_CAPACITY),
timeout: FERRY_TIMEOUT,
travel_time: HARBOR_DISTANCE,
piers: ports.iter().skip(i)
.chain(ports.iter().take(i)).cloned().collect()
};
sim.activate(ferry.actions(sim));
}
// await the end of the simulation
sim.advance(duration).await;
// take cars into account that weren't picked up by a ferry
for port in ports {
for _ in 0..port.len() {
let car = port.recv().await.unwrap();
sim.shared().car_wait_time.tabulate(sim.now() - car.arrival_time);
}
}
}
use criterion::{Criterion, BenchmarkId, criterion_group, criterion_main,
PlotConfiguration, AxisScale, SamplingMode};
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()
.summary_scale(AxisScale::Logarithmic)
);
group.sampling_mode(SamplingMode::Linear);
// 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",
"/stdout",
"/noicon",
"/nowarn",
"/noxwarn",
"/#BENCH",
"slx\\philosophers.slx",
count.as_str()
];
// benchmark the SLX implementation
group.bench_function(
BenchmarkId::new("SLX", experiment_count),
|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.bench_function(
BenchmarkId::new("Rust", experiment_count),
|b| b.iter(|| (1..=experiment_count)
.into_par_iter()
.map(|experiment_no|
simulation(
// global data
Shared {
master_rng: RefCell::new(SmallRng::seed_from_u64(SEED*experiment_no)),
sim_duration: Cell::default()
},
// simulation entry point
|sim| sim.process(philosophers(sim, PHILOSOPHER_COUNT))
).sim_duration.get()
)
.fold(
|| RandomVar::new(),
|var, duration| { var.tabulate(duration); var }
)
.reduce(
|| RandomVar::new(),
|var_a, var_b| { var_a.merge(&var_b); var_a }
)
)
);
}
group.finish();
}
criterion_group!(benches, philosopher_bench);
criterion_main!(benches);
/* *********************** Dining Philosophers Example ********************** */
use simcore_rs::{SimContext, Time, RandomVar, Control, until, simulation};
use std::{rc::Rc, cell::{RefCell, Cell}};
use rand::{rngs::SmallRng, SeedableRng, Rng};
use rand_distr::{Exp, Normal, Uniform, Distribution};
use rayon::prelude::*;
struct Shared {
master_rng: RefCell<SmallRng>,
sim_duration: Cell<Time>
}
struct Table {
forks: Vec<Control<bool>>,
forks_held: Control<usize>,
forks_awaited: Control<usize>
}
impl Table {
fn new(forks: usize) -> Self {
Table {
forks: (0..forks).map(|_| Control::new(false)).collect(),
forks_held: Control::default(),
forks_awaited: Control::default()
}
}
// blocks until the requested fork is available and acquires it
async fn acquire_fork(&self, i: usize) {
let num = i % self.forks.len();
self.forks_awaited.set(self.forks_awaited.get() + 1);
until(&self.forks[num], |fork| !fork.get()).await;
self.forks[num].set(true);
self.forks_awaited.set(self.forks_awaited.get() - 1);
self.forks_held.set(self.forks_held.get() + 1);
}
// returns the requested fork to the table
fn release_fork(&self, i: usize) {
self.forks[i % self.forks.len()].set(false);
self.forks_held.set(self.forks_held.get() - 1);
}
}
struct Philosopher {
table: Rc<Table>,
seat: usize,
rng: SmallRng
}
impl Philosopher {
async fn actions(mut self, sim: SimContext<'_, Shared>) {
let thinking_duration = Exp::new(1.0).unwrap();
let artificial_delay = Uniform::new(0.1, 0.2);
let eating_duration = Normal::new(0.5, 0.2).unwrap();
loop {
// spend some time pondering the nature of things
sim.advance(thinking_duration.sample(&mut self.rng)).await;
// acquire the first fork
self.table.acquire_fork(self.seat).await;
// introduce an artificial delay to leave room for deadlocks
sim.advance(artificial_delay.sample(&mut self.rng)).await;
// acquire the second fork
self.table.acquire_fork(self.seat + 1).await;
// spend some time eating
sim.advance(
eating_duration
.sample_iter(&mut self.rng)
.find(|&val| val >= 0.0)
.unwrap()
).await;
// release the forks
self.table.release_fork(self.seat + 1);
self.table.release_fork(self.seat);
}
}
}
async fn philosophers(sim: SimContext<'_, Shared>, count: usize) {
let table = Rc::new(Table::new(count));
// create the philosopher-processes and seat them
for i in 0..count {
sim.activate(Philosopher {
table: table.clone(),
seat: i,
rng: SmallRng::from_seed(sim.shared().master_rng.borrow_mut().gen())
}.actions(sim));
}
// wait for the precise configuration indicating a deadlock
// (we technically don't really have to do this, because deadlock-detection
// is automatic since it means that no processes are scheduled and the
// simulation ends; SLX doesn't have that ability, though)
until(
(&table.forks_held, &table.forks_awaited),
|(held, awaited)|
held.get() == count && awaited.get() == count
).await;
// tabulate the current system time
sim.shared().sim_duration.set(sim.now());
}
\ No newline at end of file
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)
}
})
}
build.rs 0 → 100644
// compiles the ODEMx-library and the C++ examples to link against
fn main() {
// attempt to translate the library using the native platform's compiler
let result = cc::Build::new()
.cpp(true)
.define("RUST_FFI", None)
.includes(&[
"odemx-lite/include",
"odemx-lite/external/CppLog/include"
])
.flag_if_supported("-std=c++11")
.files(&[
"odemx-lite/src/base/Comparators.cpp",
"odemx-lite/src/base/Continuous.cpp",
"odemx-lite/src/base/DefaultSimulation.cpp",
"odemx-lite/src/base/Event.cpp",
"odemx-lite/src/base/ExecutionList.cpp",
"odemx-lite/src/base/Process.cpp",
"odemx-lite/src/base/Sched.cpp",
"odemx-lite/src/base/Scheduler.cpp",
"odemx-lite/src/base/Simulation.cpp",
"odemx-lite/src/base/continuous/Continuous.cpp",
"odemx-lite/src/base/continuous/DfDt.cpp",
"odemx-lite/src/base/continuous/JacobiMatrix.cpp",
"odemx-lite/src/base/continuous/Monitor.cpp",
"odemx-lite/src/base/continuous/ODEObject.cpp",
"odemx-lite/src/base/continuous/ODESolver.cpp",
"odemx-lite/src/base/continuous/Rate.cpp",
"odemx-lite/src/base/continuous/State.cpp",
"odemx-lite/src/base/continuous/StateEvent.cpp",
"odemx-lite/src/base/continuous/VariableContainer.cpp",
"odemx-lite/src/base/control/ControlBase.cpp",
"odemx-lite/src/coroutine/Coroutine.cpp",
"odemx-lite/src/coroutine/CoroutineContext.cpp",
"odemx-lite/src/coroutine/ucFiber.cpp",
"odemx-lite/src/data/buffer/SimRecordBuffer.cpp",
"odemx-lite/src/data/buffer/StatisticsBuffer.cpp",
"odemx-lite/src/data/LoggingManager.cpp",
"odemx-lite/src/data/ManagedChannels.cpp",
"odemx-lite/src/data/output/ErrorWriter.cpp",
"odemx-lite/src/data/output/GermanTime.cpp",
"odemx-lite/src/data/output/Iso8601Time.cpp",
"odemx-lite/src/data/output/OStreamReport.cpp",
"odemx-lite/src/data/output/OStreamWriter.cpp",
"odemx-lite/src/data/output/TimeFormat.cpp",
"odemx-lite/src/data/Producer.cpp",
"odemx-lite/src/data/Report.cpp",
"odemx-lite/src/data/ReportProducer.cpp",
"odemx-lite/src/data/ReportTable.cpp",
"odemx-lite/src/data/SimRecord.cpp",
"odemx-lite/src/data/SimRecordFilter.cpp",
"odemx-lite/src/protocol/Device.cpp",
"odemx-lite/src/protocol/Entity.cpp",
"odemx-lite/src/protocol/ErrorModelDraw.cpp",
"odemx-lite/src/protocol/Layer.cpp",
"odemx-lite/src/protocol/Medium.cpp",
"odemx-lite/src/protocol/Sap.cpp",
"odemx-lite/src/protocol/Service.cpp",
"odemx-lite/src/protocol/ServiceProvider.cpp",
"odemx-lite/src/protocol/Stack.cpp",
"odemx-lite/src/random/ContinuousConst.cpp",
"odemx-lite/src/random/ContinuousDist.cpp",
"odemx-lite/src/random/DiscreteConst.cpp",
"odemx-lite/src/random/DiscreteDist.cpp",
"odemx-lite/src/random/Dist.cpp",
"odemx-lite/src/random/DistContext.cpp",
"odemx-lite/src/random/Draw.cpp",
"odemx-lite/src/random/Erlang.cpp",
"odemx-lite/src/random/NegativeExponential.cpp",
"odemx-lite/src/random/Normal.cpp",
"odemx-lite/src/random/Poisson.cpp",
"odemx-lite/src/random/RandomInt.cpp",
"odemx-lite/src/random/Uniform.cpp",
"odemx-lite/src/statistics/Accumulate.cpp",
"odemx-lite/src/statistics/Count.cpp",
"odemx-lite/src/statistics/Histogram.cpp",
"odemx-lite/src/statistics/Regression.cpp",
"odemx-lite/src/statistics/Sum.cpp",
"odemx-lite/src/statistics/Tab.cpp",
"odemx-lite/src/statistics/Tally.cpp",
"odemx-lite/src/synchronization/Bin.cpp",
"odemx-lite/src/synchronization/CondQ.cpp",
"odemx-lite/src/synchronization/IMemory.cpp",
"odemx-lite/src/synchronization/Memory.cpp",
"odemx-lite/src/synchronization/ProcessQueue.cpp",
"odemx-lite/src/synchronization/Queue.cpp",
"odemx-lite/src/synchronization/Res.cpp",
"odemx-lite/src/synchronization/Timer.cpp",
"odemx-lite/src/synchronization/Wait.cpp",
"odemx-lite/src/synchronization/WaitQ.cpp",
// example scenarios
"cpp/Barbershop/main.cpp",
"cpp/Ferry/main.cpp",
"cpp/Philosophers/main.cpp",
])
.try_compile("odemx");
// there is no need to stop the build altogether if the compilation failed
if let Err(msg) = result {
println!("cargo:warning=Compiling ODEMx-lite has failed.");
println!("cargo:warning=This library will still work, \
the C++ benchmarks will not!");
println!("cargo:warning={}", msg);
}
}
use simcore_rs::{Time, SimContext, Facility, RandomVar, simulation};
use simcore_rs::{Time, SimContext, Facility, RandomVar};
use rand::{distributions::Uniform, rngs::SmallRng, SeedableRng, Rng};
/// Globally shared data.
......@@ -8,9 +8,8 @@ struct Shared {
}
// helper constants
const SIM_DURATION : Time = 60.0*24.0*7.0*3.0; // simulation runs over 3 weeks
const SEED_A : u64 = 100000;
const SEED_S : u64 = 200000;
const SEED_A: u64 = 100000;
const SEED_S: u64 = 200000;
/// Customer process with access to the barber and a random processing delay.
struct Customer { delay: Time }
......@@ -40,7 +39,7 @@ async fn barbershop(sim: SimContext<'_,Shared>, duration: Time) {
let dist_s = Uniform::new(12.0, 18.0);
loop {
sim.advance(rng_a.sample(dist_a)).await;
if sim.now() >= SIM_DURATION { return; }
if sim.now() >= duration { return; }
sim.activate(Customer {
delay: rng_s.sample(dist_s)
}.actions(sim));
......@@ -54,14 +53,107 @@ async fn barbershop(sim: SimContext<'_,Shared>, duration: Time) {
sim.shared().joe.seize().await;
}
#[cfg(not(test))]
fn main() {
let result = simulation(
let result = simcore_rs::simulation(
// global data
Shared { joe: Facility::new(), wait_time: RandomVar::new() },
// simulation entry point
|sim| sim.process(barbershop(sim, SIM_DURATION))
|sim| sim.process(barbershop(sim, 60.0*24.0*7.0*3.0))
);
// print statistics
println!("wait_time: {:#.3}", result.wait_time);
}
#[cfg(test)]
criterion::criterion_main!(bench::benches);
#[cfg(test)]
mod slx;
#[cfg(test)]
mod bench {
use super::*;
use criterion::{Criterion, BenchmarkId, BatchSize, PlotConfiguration,
AxisScale, SamplingMode, criterion_group};
use std::time::Duration;
mod odemx {
use std::os::raw::c_double;
#[link(name = "odemx", kind = "static")]
extern {
pub fn barbershop(duration: c_double);
}
}
const SLX_PATH: &'static str = "C:/Wolverine/SLX/se64.exe";
const RANGE: u32 = 10;
const STEP: Time = 1000.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()
.summary_scale(AxisScale::Logarithmic)
);
group.sampling_mode(SamplingMode::Linear);
group.measurement_time(Duration::from_secs(300));
// vary in the length of the simulation run
for sim_duration in (0..RANGE).map(|c| Time::from(1 << c)*STEP) {
let duration = sim_duration.to_string();
let args = [
"/silent",
"/stdout",
"/noicon",
"/nowarn",
"/noxwarn",
"/#BENCH",
"slx\\barbershop.slx",
duration.as_str()
];
// benchmark the SLX implementation
group.bench_function(
BenchmarkId::new("SLX", sim_duration),
|b| b.iter_custom(|iters|
slx::slx_bench(
SLX_PATH,
&args,
iters as usize
).expect("couldn't benchmark the SLX program")
)
);
// benchmark the C++ implementation
group.bench_function(
BenchmarkId::new("ODEMx", sim_duration),
|b| b.iter(
|| unsafe { odemx::barbershop(sim_duration as _) }
)
);
// benchmark the Rust implementation
group.bench_function(
BenchmarkId::new("Rust", sim_duration),
|b| b.iter_batched(
|| Shared { joe: Facility::new(), wait_time: RandomVar::new() },
|shared| simcore_rs::simulation(
shared,
|sim| sim.process(barbershop(sim, sim_duration))
),
BatchSize::SmallInput
)
);
}
group.finish();
}
criterion_group!(benches, barbershop_bench);
}
use simcore_rs::{Time, SimContext, Sender, Receiver, RandomVar, channel, simulation};
use simcore_rs::{Time, SimContext, Sender, Receiver, RandomVar, channel};
use rand::{rngs::SmallRng, SeedableRng, Rng};
use rand_distr::{Exp, Normal, Distribution};
use std::cell::RefCell;
const SEED: u64 = 100000;
const FERRY_COUNT: usize = 2;
const HARBOR_COUNT: usize = 4;
const HARBOR_DISTANCE: Time = 10.0;
const FERRY_TIMEOUT : Time = 5.0;
const FERRY_CAPACITY : usize = 5;
......@@ -122,12 +125,9 @@ async fn ferry(sim: SimContext<'_,Shared>, duration: Time, ferries: usize, harbo
}
}
const SEED: u64 = 100000;
const FERRY_COUNT: usize = 2;
const HARBOR_COUNT: usize = 4;
#[cfg(not(test))]
fn main() {
let result = simulation(
let result = simcore_rs::simulation(
// global data
Shared {
master_rng: RefCell::new(SmallRng::seed_from_u64(SEED)),
......@@ -136,7 +136,9 @@ fn main() {
car_wait_time: RandomVar::new(),
},
// simulation entry point
|sim| sim.process(ferry(sim, 24.0*60.0*7.0, FERRY_COUNT, HARBOR_COUNT))
|sim| sim.process(
ferry(sim, 24.0*60.0*7.0, FERRY_COUNT, HARBOR_COUNT)
)
);
println!("Number of harbors: {}", HARBOR_COUNT);
......@@ -145,3 +147,106 @@ fn main() {
println!("Ferry cargo len: {:#.3}", result.ferry_cargo_len);
println!("Ferry load time: {:#.3}", result.ferry_load_time);
}
#[cfg(test)]
criterion::criterion_main!(bench::benches);
#[cfg(test)]
mod slx;
#[cfg(test)]
mod bench {
use super::*;
use criterion::{Criterion, BenchmarkId, BatchSize, PlotConfiguration,
AxisScale, SamplingMode, criterion_group};
use std::time::Duration;
mod odemx {
use std::os::raw::{c_double, c_uint};
#[link(name = "odemx", kind = "static")]
extern {
pub fn ferry(duration: c_double, ferries: c_uint, harbors: c_uint);
}
}
const SLX_PATH: &'static str = "C:/Wolverine/SLX/se64.exe";
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()
.summary_scale(AxisScale::Logarithmic)
);
group.sampling_mode(SamplingMode::Linear);
group.measurement_time(Duration::from_secs(600));
// vary in the length of the simulation run
for (i, sim_duration) in (0..RANGE).map(|c| Time::from(1 << c)*STEP).enumerate() {
let duration = sim_duration.to_string();
let args = [
"/silent",
"/stdout",
"/noicon",
"/nowarn",
"/noxwarn",
"/#BENCH",
"slx\\ferry.slx",
duration.as_str()
];
// benchmark the SLX implementation
group.bench_function(
BenchmarkId::new("SLX", sim_duration),
|b| b.iter_custom(|iters|
slx::slx_bench(
SLX_PATH,
&args,
iters as usize
).expect("couldn't benchmark the SLX program")
)
);
// benchmark the C++ implementation
group.bench_function(
BenchmarkId::new("ODEMx", sim_duration),
|b| b.iter(|| unsafe {
odemx::ferry(
sim_duration as _,
FERRY_COUNT as _,
HARBOR_COUNT as _
)
})
);
// benchmark the Rust implementation
group.bench_function(
BenchmarkId::new("Rust", sim_duration),
|b| b.iter_batched(
|| Shared {
master_rng: RefCell::new(SmallRng::seed_from_u64(SEED*((i+1) as u64))),
ferry_cargo_len: RandomVar::new(),
ferry_load_time: RandomVar::new(),
car_wait_time: RandomVar::new(),
},
|shared| simcore_rs::simulation(
shared,
|sim| sim.process(
ferry(sim, sim_duration, FERRY_COUNT, HARBOR_COUNT)
)
),
BatchSize::SmallInput
)
);
}
group.finish();
}
criterion_group!(benches, ferry_bench);
}
use simcore_rs::{SimContext, Time, RandomVar, Control, until, simulation};
use simcore_rs::{SimContext, Time, RandomVar, Control, until};
use std::{rc::Rc, cell::{RefCell, Cell}};
use rand::{rngs::SmallRng, SeedableRng, Rng};
use rand_distr::{Exp, Normal, Uniform, Distribution};
use rayon::prelude::*;
const PHILOSOPHER_COUNT: usize = 5;
const SEED: u64 = 100000;
struct Shared {
master_rng: RefCell<SmallRng>,
sim_duration: Cell<Time>
......@@ -81,7 +84,7 @@ impl Philosopher {
}
}
async fn philosophers(sim: SimContext<'_, Shared>, count: usize) {
async fn run_once(sim: SimContext<'_, Shared>, count: usize) {
let table = Rc::new(Table::new(count));
// create the philosopher-processes and seat them
......@@ -94,9 +97,10 @@ async fn philosophers(sim: SimContext<'_, Shared>, count: usize) {
}
// wait for the precise configuration indicating a deadlock
// (we technically don't really have to do this, because deadlock-detection
// is automatic since it means that no processes are scheduled and the
// simulation ends; SLX doesn't have that ability, though)
// (we technically don't have to do this, because deadlocks imply that no
// processes can advance, causing the simulation to end anyways;
// SLX doesn't allow us to record the time of that event, so we can't use
// it here either)
until(
(&table.forks_held, &table.forks_awaited),
|(held, awaited)|
......@@ -107,22 +111,21 @@ async fn philosophers(sim: SimContext<'_, Shared>, count: usize) {
sim.shared().sim_duration.set(sim.now());
}
const SEED : u64 = 100000;
const EXPERIMENT_COUNT : u64 = 500;
const PHILOSOPHER_COUNT : usize = 5;
fn main() {
let sim_duration = (1..=EXPERIMENT_COUNT).into_par_iter()
fn philosophers(count: usize, reruns: usize) -> RandomVar {
// use thread-based parallelism to concurrently run simulation models
(1..=reruns)
.into_par_iter()
.map(|i|
simulation(
simcore_rs::simulation(
// global data
Shared {
master_rng: RefCell::new(SmallRng::seed_from_u64(i*SEED)),
master_rng: RefCell::new(SmallRng::seed_from_u64(i as u64 * SEED)),
sim_duration: Cell::default()
},
// simulation entry point
|sim| sim.process(philosophers(sim, PHILOSOPHER_COUNT))
).sim_duration.get()
|sim|
sim.process(run_once(sim, count))
).sim_duration.get()
)
.fold(
|| RandomVar::new(),
......@@ -131,7 +134,100 @@ fn main() {
.reduce(
|| RandomVar::new(),
|var_a, var_b| { var_a.merge(&var_b); var_a }
);
)
}
#[cfg(not(test))]
fn main() {
const EXPERIMENT_COUNT : usize = 500;
let sim_duration = philosophers(PHILOSOPHER_COUNT, EXPERIMENT_COUNT);
println!("sim_duration: {:#}", sim_duration);
}
#[cfg(test)]
criterion::criterion_main!(bench::benches);
#[cfg(test)]
mod slx;
#[cfg(test)]
mod bench {
use super::*;
use criterion::{Criterion, BenchmarkId, PlotConfiguration, AxisScale,
SamplingMode, criterion_group};
mod odemx {
use std::os::raw::c_uint;
#[link(name = "odemx", kind = "static")]
extern {
pub fn philosophers(count: c_uint, reruns: c_uint);
}
}
const SLX_PATH: &'static str = "C:/Wolverine/SLX/se64.exe";
const RANGE: u32 = 10;
const STEP: usize = 4;
fn philosopher_bench(c: &mut Criterion) {
let mut group = c.benchmark_group("Philosophers");
// set-up the benchmark parameters
group.confidence_level(0.99);
group.plot_config(
PlotConfiguration::default()
.summary_scale(AxisScale::Logarithmic)
);
group.sampling_mode(SamplingMode::Linear);
// vary the number of performed reruns in each experiment
for experiment_count in (0..RANGE).map(|c| (1 << c)*STEP) {
let count = experiment_count.to_string();
let args = [
"/silent",
"/stdout",
"/noicon",
"/nowarn",
"/noxwarn",
"/#BENCH",
"slx\\philosophers.slx",
count.as_str()
];
// benchmark the SLX implementation
group.bench_function(
BenchmarkId::new("SLX", experiment_count),
|b| b.iter_custom(|iters|
slx::slx_bench(
SLX_PATH,
&args,
iters as usize
).expect("couldn't benchmark the SLX program")
)
);
// benchmark the C++ implementation
group.bench_function(
BenchmarkId::new("ODEMx", experiment_count),
|b| b.iter(|| unsafe {
odemx::philosophers(
PHILOSOPHER_COUNT as _,
experiment_count as _
)
})
);
// benchmark the Rust implementation
group.bench_function(
BenchmarkId::new("Rust", experiment_count),
|b| b.iter(||
philosophers(PHILOSOPHER_COUNT, experiment_count)
)
);
}
group.finish();
}
criterion_group!(benches, philosopher_bench);
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment