diff --git a/Cargo.lock b/Cargo.lock
index 0b71cef861ef3f140f5b643769e510319195ad07..bd362d1de534d1e5a889868b6ef198a4424f6dde 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
diff --git a/Cargo.toml b/Cargo.toml
index dcce310c19f2815e01690ca5e238f3afda7b139f..3ffe8c1447f603cb07d6aa811f82ce931d79df8d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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
diff --git a/benches/barbershop.rs b/benches/barbershop.rs
deleted file mode 100644
index 179410d8de3e021753f69fbd9f40ad4849f2f1bf..0000000000000000000000000000000000000000
--- a/benches/barbershop.rs
+++ /dev/null
@@ -1,121 +0,0 @@
-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;
-}
diff --git a/benches/ferry.rs b/benches/ferry.rs
deleted file mode 100644
index c7c46c60b9d28809139361470e17b95a1bf2db96..0000000000000000000000000000000000000000
--- a/benches/ferry.rs
+++ /dev/null
@@ -1,199 +0,0 @@
-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);
-		}
-	}
-}
diff --git a/benches/philosophers.rs b/benches/philosophers.rs
deleted file mode 100644
index cb4aae7832579dc0c3c98274cea4f08a190c2707..0000000000000000000000000000000000000000
--- a/benches/philosophers.rs
+++ /dev/null
@@ -1,191 +0,0 @@
-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
diff --git a/benches/support/mod.rs b/benches/support/mod.rs
deleted file mode 100644
index f38c7cd1ad9006fe54c2a3f687f526c62dd67e05..0000000000000000000000000000000000000000
--- a/benches/support/mod.rs
+++ /dev/null
@@ -1,79 +0,0 @@
-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/build.rs b/build.rs
new file mode 100644
index 0000000000000000000000000000000000000000..5e05786f8125a742f92cdb22ca8e2ea1d2690669
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,106 @@
+
+// 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);
+	}
+}
diff --git a/examples/barbershop.rs b/examples/barbershop.rs
index a29aa152cf482e2a1c4392c586df57d757d53df9..e30f1c822b85be6ce2c2c152e1b899f3fb816d30 100644
--- a/examples/barbershop.rs
+++ b/examples/barbershop.rs
@@ -1,4 +1,4 @@
-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);
+}
diff --git a/examples/ferry.rs b/examples/ferry.rs
index 8067af23657d4b651d49b91c82f8bfea036a13c6..e03f638199961328ccb3ba8de22e187615c93cc2 100644
--- a/examples/ferry.rs
+++ b/examples/ferry.rs
@@ -1,8 +1,11 @@
-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);
+}
diff --git a/examples/philosophers.rs b/examples/philosophers.rs
index cdcadccd9ceea17dba0818a72459dab9489610c3..c89973f078436a2efe3d829993627b464b71a6de 100644
--- a/examples/philosophers.rs
+++ b/examples/philosophers.rs
@@ -1,9 +1,12 @@
-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);
+}