Skip to content

Commit

Permalink
Add server-like benchmark.
Browse files Browse the repository at this point in the history
  • Loading branch information
cfallin committed Jan 19, 2022
1 parent f9fe6e6 commit cf6fa6e
Show file tree
Hide file tree
Showing 4 changed files with 226 additions and 98 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ debug = false # FIXME(#1813)
name = "instantiation"
harness = false

[[bench]]
name = "server"
harness = false

[[bench]]
name = "thread_eager_init"
harness = false
98 changes: 98 additions & 0 deletions benches/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Common benchmark helpers shared by multiple benchmarks.
// Not all helpers are used in all benchmarks.
#![allow(dead_code)]

use anyhow::Result;
use std::path::PathBuf;
use std::process::Command;
use wasmtime::*;
use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx};

pub fn build_wasi_example() {
println!("Building WASI example module...");
if !Command::new("cargo")
.args(&[
"build",
"--release",
"-p",
"example-wasi-wasm",
"--target",
"wasm32-wasi",
])
.spawn()
.expect("failed to run cargo to build WASI example")
.wait()
.expect("failed to wait for cargo to build")
.success()
{
panic!("failed to build WASI example for target `wasm32-wasi`");
}

std::fs::copy(
"target/wasm32-wasi/release/wasi.wasm",
"benches/instantiation/wasi.wasm",
)
.expect("failed to copy WASI example module");
}

pub fn strategies() -> Vec<InstanceAllocationStrategy> {
vec![
// Skip the on-demand allocator when uffd is enabled
#[cfg(any(not(feature = "uffd"), not(target_os = "linux")))]
InstanceAllocationStrategy::OnDemand,
InstanceAllocationStrategy::pooling(),
]
}

pub fn modules() -> Vec<&'static str> {
vec![
"empty.wat",
"small_memory.wat",
"data_segments.wat",
"wasi.wasm",
]
}

pub fn make_engine(strategy: &InstanceAllocationStrategy, is_async: bool) -> Result<Engine> {
let mut config = Config::default();
config.allocation_strategy(strategy.clone());
config.async_support(is_async);
Engine::new(&config)
}

pub fn load_module(engine: &Engine, module_name: &str) -> Result<(Module, Linker<WasiCtx>)> {
let mut path = PathBuf::new();
path.push("benches");
path.push("instantiation");
path.push(module_name);

let module = Module::from_file(&engine, &path)
.unwrap_or_else(|_| panic!("failed to load benchmark `{}`", path.display()));
let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap();

Ok((module, linker))
}

pub fn benchmark_name<'a>(strategy: &InstanceAllocationStrategy) -> &'static str {
match strategy {
InstanceAllocationStrategy::OnDemand => "default",
#[cfg(any(not(feature = "uffd"), not(target_os = "linux")))]
InstanceAllocationStrategy::Pooling { .. } => "pooling",
#[cfg(all(feature = "uffd", target_os = "linux"))]
InstanceAllocationStrategy::Pooling { .. } => "uffd",
}
}

pub fn instantiate(linker: &Linker<WasiCtx>, module: &Module) -> Result<Instance> {
let wasi = WasiCtxBuilder::new().build();
let mut store = Store::new(module.engine(), wasi);
linker.instantiate(&mut store, module)
}

pub fn instantiate_pre(linker: &Linker<WasiCtx>, module: &Module) -> Result<InstancePre<WasiCtx>> {
let wasi = WasiCtxBuilder::new().build();
let mut store = Store::new(module.engine(), wasi);
linker.instantiate_pre(&mut store, module)
}
123 changes: 25 additions & 98 deletions benches/instantiation.rs
Original file line number Diff line number Diff line change
@@ -1,55 +1,27 @@
use anyhow::Result;
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use rayon::{prelude::*, ThreadPoolBuilder};
use std::{path::PathBuf, process::Command};
use wasmtime::*;
use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx};

fn instantiate(linker: &Linker<WasiCtx>, module: &Module) -> Result<()> {
let wasi = WasiCtxBuilder::new().build();
let mut store = Store::new(module.engine(), wasi);
let _instance = linker.instantiate(&mut store, module)?;
// Tell rustfmt to skip this module reference; otherwise it can't seem to find it (`cargo fmt` says
// ".../wasmtime/benches does not exist".
#[rustfmt::skip]
mod common;

Ok(())
}

fn benchmark_name<'a>(strategy: &InstanceAllocationStrategy) -> &'static str {
match strategy {
InstanceAllocationStrategy::OnDemand => "default",
#[cfg(any(not(feature = "uffd"), not(target_os = "linux")))]
InstanceAllocationStrategy::Pooling { .. } => "pooling",
#[cfg(all(feature = "uffd", target_os = "linux"))]
InstanceAllocationStrategy::Pooling { .. } => "uffd",
}
}

fn bench_sequential(c: &mut Criterion, modules: &[&str]) {
fn bench_sequential(c: &mut Criterion) {
let mut group = c.benchmark_group("sequential");

for strategy in &[
// Skip the on-demand allocator when uffd is enabled
#[cfg(any(not(feature = "uffd"), not(target_os = "linux")))]
InstanceAllocationStrategy::OnDemand,
InstanceAllocationStrategy::pooling(),
] {
for file_name in modules {
let mut path = PathBuf::new();
path.push("benches");
path.push("instantiation");
path.push(file_name);
for strategy in common::strategies() {
for file_name in common::modules() {
let engine = common::make_engine(&strategy, false).unwrap();
let (module, linker) = common::load_module(&engine, file_name).unwrap();

let mut config = Config::default();
config.allocation_strategy(strategy.clone());

let engine = Engine::new(&config).expect("failed to create engine");
let module = Module::from_file(&engine, &path)
.unwrap_or_else(|_| panic!("failed to load benchmark `{}`", path.display()));
let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap();

group.bench_function(BenchmarkId::new(benchmark_name(strategy), file_name), |b| {
b.iter(|| instantiate(&linker, &module).expect("failed to instantiate module"));
});
group.bench_function(
BenchmarkId::new(common::benchmark_name(&strategy), file_name),
|b| {
b.iter(|| {
common::instantiate(&linker, &module).expect("failed to instantiate module")
});
},
);
}
}

Expand All @@ -61,20 +33,10 @@ fn bench_parallel(c: &mut Criterion) {

let mut group = c.benchmark_group("parallel");

for strategy in &[
// Skip the on-demand allocator when uffd is enabled
#[cfg(any(not(feature = "uffd"), not(target_os = "linux")))]
InstanceAllocationStrategy::OnDemand,
InstanceAllocationStrategy::pooling(),
] {
let mut config = Config::default();
config.allocation_strategy(strategy.clone());

let engine = Engine::new(&config).expect("failed to create engine");
let module = Module::from_file(&engine, "benches/instantiation/wasi.wasm")
.expect("failed to load WASI example module");
let mut linker = Linker::new(&engine);
wasmtime_wasi::add_to_linker(&mut linker, |cx| cx).unwrap();
for strategy in common::strategies() {
let engine = common::make_engine(&strategy, false).unwrap();
let (module, linker) =
common::load_module(&engine, "benches/instantiation/wasi.wasm").unwrap();

for threads in 1..=num_cpus::get_physical() {
let pool = ThreadPoolBuilder::new()
Expand All @@ -84,7 +46,7 @@ fn bench_parallel(c: &mut Criterion) {

group.bench_function(
BenchmarkId::new(
benchmark_name(strategy),
common::benchmark_name(&strategy),
format!(
"{} instances with {} thread{}",
PARALLEL_INSTANCES,
Expand All @@ -96,7 +58,7 @@ fn bench_parallel(c: &mut Criterion) {
b.iter(|| {
pool.install(|| {
(0..PARALLEL_INSTANCES).into_par_iter().for_each(|_| {
instantiate(&linker, &module)
common::instantiate(&linker, &module)
.expect("failed to instantiate module");
})
})
Expand All @@ -109,44 +71,9 @@ fn bench_parallel(c: &mut Criterion) {
group.finish();
}

fn build_wasi_example() {
println!("Building WASI example module...");
if !Command::new("cargo")
.args(&[
"build",
"--release",
"-p",
"example-wasi-wasm",
"--target",
"wasm32-wasi",
])
.spawn()
.expect("failed to run cargo to build WASI example")
.wait()
.expect("failed to wait for cargo to build")
.success()
{
panic!("failed to build WASI example for target `wasm32-wasi`");
}

std::fs::copy(
"target/wasm32-wasi/release/wasi.wasm",
"benches/instantiation/wasi.wasm",
)
.expect("failed to copy WASI example module");
}

fn bench_instantiation(c: &mut Criterion) {
build_wasi_example();
bench_sequential(
c,
&[
"empty.wat",
"small_memory.wat",
"data_segments.wat",
"wasi.wasm",
],
);
common::build_wasi_example();
bench_sequential(c);
bench_parallel(c);
}

Expand Down
99 changes: 99 additions & 0 deletions benches/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use criterion::{criterion_group, criterion_main, Criterion};
use std::{sync::Arc, time::Instant};
use wasmtime::*;
use wasmtime_wasi::{sync::WasiCtxBuilder, WasiCtx};

// Tell rustfmt to skip this module reference; otherwise it can't seem to find it (`cargo fmt` says
// ".../wasmtime/benches does not exist".
#[rustfmt::skip]
mod common;

struct Server {
permits: tokio::sync::Semaphore,
engine: Engine,
modules: Vec<Module>,
instance_pres: Vec<InstancePre<WasiCtx>>,
}

impl Server {
async fn job(self: Arc<Self>, index: usize) {
let _permit = self.permits.acquire().await.unwrap();
let ipre = &self.instance_pres[index % self.modules.len()];
let wasi = WasiCtxBuilder::new().build();
let mut store = Store::new(&self.engine, wasi);
let instance = ipre.instantiate_async(&mut store).await.unwrap();
let start_func = instance.get_func(&mut store, "_start").unwrap();
start_func
.call_async(&mut store, &[], &mut [])
.await
.unwrap();
}
}

fn run_server(
strategy: &InstanceAllocationStrategy,
filenames: &[&str],
occupancy: usize,
instantiations: usize,
) {
let engine = common::make_engine(strategy, /* async = */ true).unwrap();
let mut instance_pres = vec![];
let mut modules = vec![];
for filename in filenames {
let (module, linker) = common::load_module(&engine, filename).unwrap();
let instance_pre = common::instantiate_pre(&linker, &module).unwrap();
modules.push(module);
instance_pres.push(instance_pre);
}

let server = Arc::new(Server {
permits: tokio::sync::Semaphore::new(occupancy),
engine,
modules,
instance_pres,
});

// Spawn an initial batch of jobs up to the
let server_clone = server.clone();

let rt = tokio::runtime::Runtime::new().unwrap();
rt.block_on(async move {
for i in 0..instantiations {
let server = server_clone.clone();
tokio::spawn(server.job(i));
}
});
}

fn bench_server(c: &mut Criterion) {
common::build_wasi_example();

let modules = vec!["wasi.wasm"];
let occupancy = 1000;

for strategy in common::strategies() {
c.bench_function(
&format!(
"strategy {}, occupancy {}, benches {:?}",
common::benchmark_name(&strategy),
occupancy,
modules,
),
|b| {
b.iter_custom(|iters| {
let start = Instant::now();
run_server(
&strategy,
&modules,
occupancy,
/* instantiations = */ iters as usize,
);
start.elapsed()
});
},
);
}
}

criterion_group!(benches, bench_server);
criterion_main!(benches);

0 comments on commit cf6fa6e

Please sign in to comment.