Path: blob/main/examples/fast_instantiation.rs
1685 views
//! Tuning Wasmtime for fast instantiation.12use anyhow::anyhow;3use wasmtime::{4Config, Engine, InstanceAllocationStrategy, Linker, Module, PoolingAllocationConfig, Result,5Store,6};78fn main() -> Result<()> {9let mut config = Config::new();1011// Configure and enable the pooling allocator with space for 100 memories of12// up to 2GiB in size, 100 tables holding up to 5000 elements, and with a13// limit of no more than 100 concurrent instances.14let mut pool = PoolingAllocationConfig::new();15pool.total_memories(100);16pool.max_memory_size(1 << 31); // 2 GiB17pool.total_tables(100);18pool.table_elements(5000);19pool.total_core_instances(100);20config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool));2122// Enable copy-on-write heap images.23config.memory_init_cow(true);2425// Create an engine with our configuration.26let engine = Engine::new(&config)?;2728// Create a linker and populate it with all the imports needed for the Wasm29// programs we will run. In a more realistic Wasmtime embedding, this would30// probably involve adding WASI functions to the linker, for example.31let mut linker = Linker::<()>::new(&engine);32linker.func_wrap("math", "add", |a: u32, b: u32| -> u32 { a + b })?;3334// Create a new module, load a pre-compiled module from disk, or etc...35let module = Module::new(36&engine,37r#"38(module39(import "math" "add" (func $add (param i32 i32) (result i32)))40(func (export "run")41(call $add (i32.const 29) (i32.const 13))42)43)44"#,45)?;4647// Create an `InstancePre` for our module, doing import resolution and48// type-checking ahead-of-time and removing it from the instantiation49// critical path.50let instance_pre = linker.instantiate_pre(&module)?;5152// Now we can very quickly instantiate our module, so long as we have no53// more than 100 concurrent instances at a time!54//55// For example, we can spawn 100 threads and have each of them instantiate56// and run our Wasm module in a loop.57//58// In a real Wasmtime embedding, this would be doing something like handling59// new HTTP requests, game events, or etc... instead of just calling the60// same function. A production embedding would likely also be using async,61// in which case it would want some sort of back-pressure mechanism (like a62// semaphore) on incoming tasks to avoid attempting to allocate more than63// the pool's maximum-supported concurrent instances (at which point,64// instantiation will start returning errors).65let handles: Vec<std::thread::JoinHandle<Result<()>>> = (0..100)66.map(|_| {67let engine = engine.clone();68let instance_pre = instance_pre.clone();69std::thread::spawn(move || -> Result<()> {70for _ in 0..999 {71// Create a new store for this instance.72let mut store = Store::new(&engine, ());73// Instantiate our module in this store.74let instance = instance_pre.instantiate(&mut store)?;75// Call the instance's `run` function!76let _result = instance77.get_typed_func::<(), i32>(&mut store, "run")?78.call(&mut store, ());79}80Ok(())81})82})83.collect();8485// Wait for the threads to finish.86for h in handles.into_iter() {87h.join().map_err(|_| anyhow!("thread panicked!"))??;88}8990Ok(())91}929394