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