1 use criterion::{Criterion, criterion_group, criterion_main};
2 use std::thread;
3 use std::time::{Duration, Instant};
4 use wasmtime::*;
5 
measure_execution_time(c: &mut Criterion)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.
duration_of_call(engine: &Engine, module: &Module) -> Duration51 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.
lazy_thread_instantiate(engine: Engine, module: Module) -> Duration66 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.
eager_thread_instantiate(engine: Engine, module: Module) -> (Duration, Duration)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 
test_setup() -> (Engine, Module)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