1 use criterion::{Criterion, criterion_group, criterion_main}; 2 use std::thread; 3 use std::time::{Duration, Instant}; 4 use wasmtime::*; 5 6 fn measure_execution_time(c: &mut Criterion) { 7 // Baseline performance: a single measurement covers both initializing 8 // thread local resources and executing the first call. 9 // 10 // The other two bench functions should sum to this duration. 11 c.bench_function("lazy initialization at call", move |b| { 12 let (engine, module) = test_setup(); 13 b.iter_custom(move |iters| { 14 (0..iters) 15 .map(|_| lazy_thread_instantiate(engine.clone(), module.clone())) 16 .sum() 17 }) 18 }); 19 20 // Using Engine::tls_eager_initialize: measure how long eager 21 // initialization takes on a new thread. 22 c.bench_function("eager initialization", move |b| { 23 let (engine, module) = test_setup(); 24 b.iter_custom(move |iters| { 25 (0..iters) 26 .map(|_| { 27 let (init, _call) = eager_thread_instantiate(engine.clone(), module.clone()); 28 init 29 }) 30 .sum() 31 }) 32 }); 33 34 // Measure how long the first call takes on a thread after it has been 35 // eagerly initialized. 36 c.bench_function("call after eager initialization", move |b| { 37 let (engine, module) = test_setup(); 38 b.iter_custom(move |iters| { 39 (0..iters) 40 .map(|_| { 41 let (_init, call) = eager_thread_instantiate(engine.clone(), module.clone()); 42 call 43 }) 44 .sum() 45 }) 46 }); 47 } 48 49 /// Creating a store and measuring the time to perform a call is the same behavior 50 /// in both setups. 51 fn duration_of_call(engine: &Engine, module: &Module) -> Duration { 52 let mut store = Store::new(engine, ()); 53 let inst = Instance::new(&mut store, module, &[]).expect("instantiate"); 54 let f = inst.get_func(&mut store, "f").expect("get f"); 55 let f = f.typed::<(), ()>(&store).expect("type f"); 56 57 let call = Instant::now(); 58 f.call(&mut store, ()).expect("call f"); 59 call.elapsed() 60 } 61 62 /// When wasmtime first runs a function on a thread, it needs to initialize 63 /// some thread-local resources and install signal handlers. This benchmark 64 /// spawns a new thread, and returns the duration it took to execute the first 65 /// function call made on that thread. 66 fn lazy_thread_instantiate(engine: Engine, module: Module) -> Duration { 67 thread::spawn(move || duration_of_call(&engine, &module)) 68 .join() 69 .expect("thread joins") 70 } 71 /// This benchmark spawns a new thread, and records the duration to eagerly 72 /// initializes the thread local resources. It then creates a store and 73 /// instance, and records the duration it took to execute the first function 74 /// call. 75 fn eager_thread_instantiate(engine: Engine, module: Module) -> (Duration, Duration) { 76 thread::spawn(move || { 77 let init_start = Instant::now(); 78 Engine::tls_eager_initialize(); 79 let init_duration = init_start.elapsed(); 80 81 (init_duration, duration_of_call(&engine, &module)) 82 }) 83 .join() 84 .expect("thread joins") 85 } 86 87 fn test_setup() -> (Engine, Module) { 88 // We only expect to create one Instance at a time, with a single memory. 89 let pool_count = 10; 90 91 let mut pool = PoolingAllocationConfig::default(); 92 pool.total_memories(pool_count) 93 .total_stacks(pool_count) 94 .total_tables(pool_count); 95 let mut config = Config::new(); 96 config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool)); 97 let engine = Engine::new(&config).unwrap(); 98 99 // The module has a memory (shouldn't matter) and a single function which is a no-op. 100 let module = Module::new(&engine, r#"(module (memory 1) (func (export "f")))"#).unwrap(); 101 (engine, module) 102 } 103 104 criterion_group!(benches, measure_execution_time); 105 criterion_main!(benches); 106