Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/benches/call.rs
3073 views
1
use criterion::measurement::WallTime;
2
use criterion::{BenchmarkGroup, Criterion, criterion_group, criterion_main};
3
use std::fmt::Debug;
4
use std::future::Future;
5
use std::pin::Pin;
6
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
7
use std::time::Instant;
8
use wasmtime::*;
9
10
criterion_main!(benches);
11
criterion_group!(benches, measure_execution_time);
12
13
fn measure_execution_time(c: &mut Criterion) {
14
host_to_wasm(c);
15
wasm_to_host(c);
16
17
#[cfg(feature = "component-model")]
18
component::measure_execution_time(c);
19
20
indirect::measure_execution_time(c);
21
}
22
23
#[derive(Copy, Clone)]
24
enum IsAsync {
25
Yes,
26
YesPooling,
27
No,
28
NoPooling,
29
}
30
31
impl IsAsync {
32
fn desc(&self) -> &str {
33
match self {
34
IsAsync::Yes => "async",
35
IsAsync::YesPooling => "async-pool",
36
IsAsync::No => "sync",
37
IsAsync::NoPooling => "sync-pool",
38
}
39
}
40
fn use_async(&self) -> bool {
41
match self {
42
IsAsync::Yes | IsAsync::YesPooling => true,
43
IsAsync::No | IsAsync::NoPooling => false,
44
}
45
}
46
}
47
48
fn engines() -> Vec<(Engine, IsAsync)> {
49
let mut config = Config::new();
50
51
#[cfg(feature = "component-model")]
52
config.wasm_component_model(true);
53
54
let mut pool = PoolingAllocationConfig::default();
55
if std::env::var("WASMTIME_TEST_FORCE_MPK").is_ok() {
56
pool.memory_protection_keys(Enabled::Yes);
57
}
58
59
vec![
60
(Engine::new(&config).unwrap(), IsAsync::No),
61
(
62
Engine::new(
63
config
64
.clone()
65
.allocation_strategy(InstanceAllocationStrategy::Pooling(pool.clone())),
66
)
67
.unwrap(),
68
IsAsync::NoPooling,
69
),
70
(Engine::new(&config).unwrap(), IsAsync::Yes),
71
(
72
Engine::new(config.allocation_strategy(InstanceAllocationStrategy::Pooling(pool)))
73
.unwrap(),
74
IsAsync::YesPooling,
75
),
76
]
77
}
78
79
/// Benchmarks the overhead of calling WebAssembly from the host in various
80
/// configurations.
81
fn host_to_wasm(c: &mut Criterion) {
82
for (engine, is_async) in engines() {
83
let mut store = Store::new(&engine, ());
84
let module = Module::new(
85
&engine,
86
r#"(module
87
(func (export "nop"))
88
(func (export "nop-params-and-results") (param i32 i64) (result f32)
89
f32.const 0)
90
)"#,
91
)
92
.unwrap();
93
let instance = if is_async.use_async() {
94
run_await(Instance::new_async(&mut store, &module, &[])).unwrap()
95
} else {
96
Instance::new(&mut store, &module, &[]).unwrap()
97
};
98
99
let bench_calls = |group: &mut BenchmarkGroup<'_, WallTime>, store: &mut Store<()>| {
100
// Bench the overhead of a function that has no parameters or results
101
bench_host_to_wasm::<(), ()>(group, store, &instance, is_async, "nop", (), ());
102
// Bench the overhead of a function that has some parameters and just
103
// one result (will use the raw system-v convention on applicable
104
// platforms).
105
bench_host_to_wasm::<(i32, i64), (f32,)>(
106
group,
107
store,
108
&instance,
109
is_async,
110
"nop-params-and-results",
111
(0, 0),
112
(0.0,),
113
);
114
};
115
116
// Bench once without any call hooks configured
117
let name = format!("{}/no-hook", is_async.desc());
118
bench_calls(&mut c.benchmark_group(&name), &mut store);
119
120
// Bench again with a "call hook" enabled
121
store.call_hook(|_, _| Ok(()));
122
let name = format!("{}/hook-sync", is_async.desc());
123
bench_calls(&mut c.benchmark_group(&name), &mut store);
124
}
125
}
126
127
fn bench_host_to_wasm<Params, Results>(
128
c: &mut BenchmarkGroup<'_, WallTime>,
129
store: &mut Store<()>,
130
instance: &Instance,
131
is_async: IsAsync,
132
name: &str,
133
typed_params: Params,
134
typed_results: Results,
135
) where
136
Params: WasmParams + ToVals + Copy + Sync,
137
Results: WasmResults + ToVals + Copy + Sync + PartialEq + Debug + 'static,
138
{
139
// Benchmark the "typed" version, which should be faster than the versions
140
// below.
141
c.bench_function(&format!("core - host-to-wasm - typed - {name}"), |b| {
142
let typed = instance
143
.get_typed_func::<Params, Results>(&mut *store, name)
144
.unwrap();
145
b.iter(|| {
146
let results = if is_async.use_async() {
147
run_await(typed.call_async(&mut *store, typed_params)).unwrap()
148
} else {
149
typed.call(&mut *store, typed_params).unwrap()
150
};
151
assert_eq!(results, typed_results);
152
})
153
});
154
155
// Benchmark the "untyped" version which should be the slowest of the three
156
// here, but not unduly slow.
157
c.bench_function(&format!("core - host-to-wasm - untyped - {name}"), |b| {
158
let untyped = instance.get_func(&mut *store, name).unwrap();
159
let params = typed_params.to_vals();
160
let expected_results = typed_results.to_vals();
161
let mut results = vec![Val::I32(0); expected_results.len()];
162
b.iter(|| {
163
if is_async.use_async() {
164
run_await(untyped.call_async(&mut *store, &params, &mut results)).unwrap();
165
} else {
166
untyped.call(&mut *store, &params, &mut results).unwrap();
167
}
168
for (expected, actual) in expected_results.iter().zip(&results) {
169
assert_vals_eq(expected, actual);
170
}
171
})
172
});
173
174
// Currently `call_async_unchecked` isn't implemented, so can't benchmark
175
// below
176
if is_async.use_async() {
177
return;
178
}
179
180
// Benchmark the "unchecked" version which should be between the above two,
181
// but is unsafe.
182
c.bench_function(&format!("core - host-to-wasm - unchecked - {name}"), |b| {
183
let untyped = instance.get_func(&mut *store, name).unwrap();
184
let params = typed_params.to_vals();
185
let results = typed_results.to_vals();
186
let mut space = vec![ValRaw::i32(0); params.len().max(results.len())];
187
b.iter(|| unsafe {
188
for (i, param) in params.iter().enumerate() {
189
space[i] = param.to_raw(&mut *store).unwrap();
190
}
191
untyped.call_unchecked(&mut *store, &mut space[..]).unwrap();
192
for (i, expected) in results.iter().enumerate() {
193
let ty = expected.ty(&store).unwrap();
194
let actual = Val::from_raw(&mut *store, space[i], ty);
195
assert_vals_eq(expected, &actual);
196
}
197
})
198
});
199
}
200
201
/// Benchmarks the overhead of calling the host from WebAssembly itself
202
fn wasm_to_host(c: &mut Criterion) {
203
let module = r#"(module
204
;; host imports with a variety of parameters/arguments
205
(import "" "nop" (func $nop))
206
(import "" "nop-params-and-results"
207
(func $nop_params_and_results (param i32 i64) (result f32))
208
)
209
210
;; "runner functions" for each of the above imports. Each runner
211
;; function takes the number of times to call the host function as
212
;; the duration of this entire loop will be measured.
213
214
(func (export "run-nop") (param i64)
215
loop
216
call $nop
217
218
local.get 0 ;; decrement & break if necessary
219
i64.const -1
220
i64.add
221
local.tee 0
222
i64.const 0
223
i64.ne
224
br_if 0
225
end
226
)
227
228
(func (export "run-nop-params-and-results") (param i64)
229
loop
230
i32.const 0 ;; always zero parameters
231
i64.const 0
232
call $nop_params_and_results
233
f32.const 0 ;; assert the correct result
234
f32.eq
235
i32.eqz
236
if
237
unreachable
238
end
239
240
local.get 0 ;; decrement & break if necessary
241
i64.const -1
242
i64.add
243
local.tee 0
244
i64.const 0
245
i64.ne
246
br_if 0
247
end
248
)
249
250
)"#;
251
252
for (engine, is_async) in engines() {
253
let mut store = Store::new(&engine, ());
254
let module = Module::new(&engine, module).unwrap();
255
256
bench_calls(
257
&mut c.benchmark_group(&format!("{}/no-hook", is_async.desc())),
258
&mut store,
259
&module,
260
is_async,
261
);
262
store.call_hook(|_, _| Ok(()));
263
bench_calls(
264
&mut c.benchmark_group(&format!("{}/hook-sync", is_async.desc())),
265
&mut store,
266
&module,
267
is_async,
268
);
269
}
270
271
// Given a `Store` will create various instances hooked up to different ways
272
// of defining host imports to benchmark their overhead.
273
fn bench_calls(
274
group: &mut BenchmarkGroup<'_, WallTime>,
275
store: &mut Store<()>,
276
module: &Module,
277
is_async: IsAsync,
278
) {
279
let engine = store.engine().clone();
280
let mut typed = Linker::new(&engine);
281
typed.func_wrap("", "nop", || {}).unwrap();
282
typed
283
.func_wrap("", "nop-params-and-results", |x: i32, y: i64| {
284
assert_eq!(x, 0);
285
assert_eq!(y, 0);
286
0.0f32
287
})
288
.unwrap();
289
let instance = if is_async.use_async() {
290
run_await(typed.instantiate_async(&mut *store, &module)).unwrap()
291
} else {
292
typed.instantiate(&mut *store, &module).unwrap()
293
};
294
bench_instance(group, store, &instance, "typed", is_async);
295
296
let mut untyped = Linker::new(&engine);
297
untyped
298
.func_new("", "nop", FuncType::new(&engine, [], []), |_, _, _| Ok(()))
299
.unwrap();
300
let ty = FuncType::new(&engine, [ValType::I32, ValType::I64], [ValType::F32]);
301
untyped
302
.func_new(
303
"",
304
"nop-params-and-results",
305
ty,
306
|_caller, params, results| {
307
assert_eq!(params.len(), 2);
308
match params[0] {
309
Val::I32(0) => {}
310
_ => unreachable!(),
311
}
312
match params[1] {
313
Val::I64(0) => {}
314
_ => unreachable!(),
315
}
316
assert_eq!(results.len(), 1);
317
results[0] = Val::F32(0);
318
Ok(())
319
},
320
)
321
.unwrap();
322
let instance = if is_async.use_async() {
323
run_await(untyped.instantiate_async(&mut *store, &module)).unwrap()
324
} else {
325
untyped.instantiate(&mut *store, &module).unwrap()
326
};
327
bench_instance(group, store, &instance, "untyped", is_async);
328
329
unsafe {
330
let mut unchecked = Linker::new(&engine);
331
unchecked
332
.func_new_unchecked("", "nop", FuncType::new(&engine, [], []), |_, _| Ok(()))
333
.unwrap();
334
let ty = FuncType::new(&engine, [ValType::I32, ValType::I64], [ValType::F32]);
335
unchecked
336
.func_new_unchecked("", "nop-params-and-results", ty, |mut caller, space| {
337
match Val::from_raw(&mut caller, space[0].assume_init(), ValType::I32) {
338
Val::I32(0) => {}
339
_ => unreachable!(),
340
}
341
match Val::from_raw(&mut caller, space[1].assume_init(), ValType::I64) {
342
Val::I64(0) => {}
343
_ => unreachable!(),
344
}
345
space[0].write(Val::F32(0).to_raw(&mut caller).unwrap());
346
Ok(())
347
})
348
.unwrap();
349
let instance = if is_async.use_async() {
350
run_await(unchecked.instantiate_async(&mut *store, &module)).unwrap()
351
} else {
352
unchecked.instantiate(&mut *store, &module).unwrap()
353
};
354
bench_instance(group, store, &instance, "unchecked", is_async);
355
}
356
357
// Only define async host imports if allowed
358
if !is_async.use_async() {
359
return;
360
}
361
362
let mut typed = Linker::<()>::new(&engine);
363
typed
364
.func_wrap_async("", "nop", |caller, _: ()| {
365
Box::new(async {
366
drop(caller);
367
})
368
})
369
.unwrap();
370
typed
371
.func_wrap_async(
372
"",
373
"nop-params-and-results",
374
|_caller, (x, y): (i32, i64)| {
375
Box::new(async move {
376
assert_eq!(x, 0);
377
assert_eq!(y, 0);
378
0.0f32
379
})
380
},
381
)
382
.unwrap();
383
let instance = run_await(typed.instantiate_async(&mut *store, &module)).unwrap();
384
bench_instance(group, store, &instance, "async-typed", is_async);
385
}
386
387
// Given a specific instance executes all of the "runner functions"
388
fn bench_instance(
389
group: &mut BenchmarkGroup<'_, WallTime>,
390
store: &mut Store<()>,
391
instance: &Instance,
392
desc: &str,
393
is_async: IsAsync,
394
) {
395
group.bench_function(&format!("core - wasm-to-host - {desc} - nop"), |b| {
396
let run = instance
397
.get_typed_func::<u64, ()>(&mut *store, "run-nop")
398
.unwrap();
399
b.iter_custom(|iters| {
400
let start = Instant::now();
401
if is_async.use_async() {
402
run_await(run.call_async(&mut *store, iters)).unwrap();
403
} else {
404
run.call(&mut *store, iters).unwrap();
405
}
406
start.elapsed()
407
})
408
});
409
group.bench_function(
410
&format!("core - wasm-to-host - {desc} - nop-params-and-results"),
411
|b| {
412
let run = instance
413
.get_typed_func::<u64, ()>(&mut *store, "run-nop-params-and-results")
414
.unwrap();
415
b.iter_custom(|iters| {
416
let start = Instant::now();
417
if is_async.use_async() {
418
run_await(run.call_async(&mut *store, iters)).unwrap();
419
} else {
420
run.call(&mut *store, iters).unwrap();
421
}
422
start.elapsed()
423
})
424
},
425
);
426
}
427
}
428
429
fn assert_vals_eq(a: &Val, b: &Val) {
430
match (a, b) {
431
(Val::I32(a), Val::I32(b)) => assert_eq!(a, b),
432
(Val::I64(a), Val::I64(b)) => assert_eq!(a, b),
433
(Val::F32(a), Val::F32(b)) => assert_eq!(a, b),
434
(Val::F64(a), Val::F64(b)) => assert_eq!(a, b),
435
_ => unimplemented!(),
436
}
437
}
438
439
trait ToVals {
440
fn to_vals(&self) -> Vec<Val>;
441
}
442
443
macro_rules! tuples {
444
($($t:ident)*) => (
445
#[allow(non_snake_case, reason = "macro-generated code")]
446
impl<$($t:Copy + Into<Val>,)*> ToVals for ($($t,)*) {
447
fn to_vals(&self) -> Vec<Val> {
448
let mut _dst = Vec::new();
449
let ($($t,)*) = *self;
450
$(_dst.push($t.into());)*
451
_dst
452
}
453
}
454
)
455
}
456
457
tuples!();
458
tuples!(A);
459
tuples!(A B);
460
tuples!(A B C);
461
462
fn run_await<F: Future>(future: F) -> F::Output {
463
let mut f = Pin::from(Box::new(future));
464
let waker = dummy_waker();
465
let mut cx = Context::from_waker(&waker);
466
loop {
467
match f.as_mut().poll(&mut cx) {
468
Poll::Ready(val) => break val,
469
Poll::Pending => {}
470
}
471
}
472
}
473
474
fn dummy_waker() -> Waker {
475
return unsafe { Waker::from_raw(clone(5 as *const _)) };
476
477
unsafe fn clone(ptr: *const ()) -> RawWaker {
478
assert_eq!(ptr as usize, 5);
479
const VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);
480
RawWaker::new(ptr, &VTABLE)
481
}
482
483
unsafe fn wake(ptr: *const ()) {
484
assert_eq!(ptr as usize, 5);
485
}
486
487
unsafe fn wake_by_ref(ptr: *const ()) {
488
assert_eq!(ptr as usize, 5);
489
}
490
491
unsafe fn drop(ptr: *const ()) {
492
assert_eq!(ptr as usize, 5);
493
}
494
}
495
496
#[cfg(feature = "component-model")]
497
mod component {
498
use super::*;
499
use wasmtime::component::{self, Component};
500
501
pub fn measure_execution_time(c: &mut Criterion) {
502
host_to_wasm(c);
503
wasm_to_host(c);
504
}
505
506
trait ToComponentVal {
507
fn to_component_val(&self) -> component::Val;
508
}
509
510
impl ToComponentVal for u32 {
511
fn to_component_val(&self) -> component::Val {
512
component::Val::U32(*self)
513
}
514
}
515
516
impl ToComponentVal for u64 {
517
fn to_component_val(&self) -> component::Val {
518
component::Val::U64(*self)
519
}
520
}
521
522
impl ToComponentVal for f32 {
523
fn to_component_val(&self) -> component::Val {
524
component::Val::Float32(*self)
525
}
526
}
527
528
trait ToComponentVals {
529
fn to_component_vals(&self) -> Vec<component::Val>;
530
}
531
532
macro_rules! tuples {
533
($($t:ident)*) => (
534
#[allow(non_snake_case, reason = "macro-generated code")]
535
impl<$($t:Copy + ToComponentVal,)*> ToComponentVals for ($($t,)*) {
536
fn to_component_vals(&self) -> Vec<component::Val> {
537
let mut _dst = Vec::new();
538
let ($($t,)*) = *self;
539
$(_dst.push($t.to_component_val());)*
540
_dst
541
}
542
}
543
)
544
}
545
546
tuples!();
547
tuples!(A);
548
tuples!(A B);
549
tuples!(A B C);
550
551
fn host_to_wasm(c: &mut Criterion) {
552
for (engine, is_async) in engines() {
553
let mut store = Store::new(&engine, ());
554
555
let component = Component::new(
556
&engine,
557
r#"
558
(component
559
(core module $m
560
(func (export "nop"))
561
(func (export "nop-params-and-results") (param i32 i64) (result f32)
562
f32.const 0
563
)
564
)
565
(core instance $i (instantiate $m))
566
(func (export "nop")
567
(canon lift (core func $i "nop"))
568
)
569
(func (export "nop-params-and-results") (param "x" u32) (param "y" u64) (result float32)
570
(canon lift (core func $i "nop-params-and-results"))
571
)
572
)
573
"#,
574
)
575
.unwrap();
576
577
let linker = component::Linker::<()>::new(&engine);
578
let instance = if is_async.use_async() {
579
run_await(linker.instantiate_async(&mut store, &component)).unwrap()
580
} else {
581
linker.instantiate(&mut store, &component).unwrap()
582
};
583
584
let bench_calls = |group: &mut BenchmarkGroup<'_, WallTime>, store: &mut Store<()>| {
585
// Bench the overhead of a function that has no parameters or results
586
bench_host_to_wasm::<(), ()>(group, store, &instance, is_async, "nop", (), ());
587
// Bench the overhead of a function that has some parameters and just
588
// one result (will use the raw system-v convention on applicable
589
// platforms).
590
bench_host_to_wasm::<(u32, u64), (f32,)>(
591
group,
592
store,
593
&instance,
594
is_async,
595
"nop-params-and-results",
596
(0, 0),
597
(0.0,),
598
);
599
};
600
601
// Bench once without any call hooks configured
602
let name = format!("{}/no-hook", is_async.desc());
603
bench_calls(&mut c.benchmark_group(&name), &mut store);
604
605
// Bench again with a "call hook" enabled
606
store.call_hook(|_, _| Ok(()));
607
let name = format!("{}/hook-sync", is_async.desc());
608
bench_calls(&mut c.benchmark_group(&name), &mut store);
609
}
610
}
611
612
fn bench_host_to_wasm<Params, Results>(
613
c: &mut BenchmarkGroup<'_, WallTime>,
614
store: &mut Store<()>,
615
instance: &component::Instance,
616
is_async: IsAsync,
617
name: &str,
618
typed_params: Params,
619
typed_results: Results,
620
) where
621
Params:
622
component::ComponentNamedList + ToComponentVals + component::Lower + Copy + Send + Sync,
623
Results: component::ComponentNamedList
624
+ ToComponentVals
625
+ component::Lift
626
+ Copy
627
+ PartialEq
628
+ Debug
629
+ Send
630
+ Sync
631
+ 'static,
632
{
633
// Benchmark the "typed" version.
634
c.bench_function(&format!("component - host-to-wasm - typed - {name}"), |b| {
635
let typed = instance
636
.get_typed_func::<Params, Results>(&mut *store, name)
637
.unwrap();
638
b.iter(|| {
639
let results = if is_async.use_async() {
640
run_await(typed.call_async(&mut *store, typed_params)).unwrap()
641
} else {
642
typed.call(&mut *store, typed_params).unwrap()
643
};
644
assert_eq!(results, typed_results);
645
})
646
});
647
648
// Benchmark the "untyped" version.
649
c.bench_function(
650
&format!("component - host-to-wasm - untyped - {name}"),
651
|b| {
652
let untyped = instance.get_func(&mut *store, name).unwrap();
653
let params = typed_params.to_component_vals();
654
let expected_results = typed_results.to_component_vals();
655
let mut results = vec![component::Val::U32(0); expected_results.len()];
656
b.iter(|| {
657
if is_async.use_async() {
658
run_await(untyped.call_async(&mut *store, &params, &mut results)).unwrap();
659
} else {
660
untyped.call(&mut *store, &params, &mut results).unwrap();
661
}
662
for (expected, actual) in expected_results.iter().zip(&results) {
663
assert_eq!(expected, actual);
664
}
665
})
666
},
667
);
668
}
669
670
fn wasm_to_host(c: &mut Criterion) {
671
let module = r#"
672
(component
673
(import "nop" (func $comp_nop))
674
(import "nop-params-and-results" (func $comp_nop_params_and_results (param "x" u32) (param "y" u64) (result float32)))
675
676
(core func $core_nop (canon lower (func $comp_nop)))
677
(core func $core_nop_params_and_results (canon lower (func $comp_nop_params_and_results)))
678
679
(core module $m
680
;; host imports with a variety of parameters/arguments
681
(import "" "nop" (func $nop))
682
(import "" "nop-params-and-results"
683
(func $nop_params_and_results (param i32 i64) (result f32))
684
)
685
686
;; "runner functions" for each of the above imports. Each runner
687
;; function takes the number of times to call the host function as
688
;; the duration of this entire loop will be measured.
689
690
(func (export "run-nop") (param i64)
691
loop
692
call $nop
693
694
local.get 0 ;; decrement & break if necessary
695
i64.const -1
696
i64.add
697
local.tee 0
698
i64.const 0
699
i64.ne
700
br_if 0
701
end
702
)
703
704
(func (export "run-nop-params-and-results") (param i64)
705
loop
706
i32.const 0 ;; always zero parameters
707
i64.const 0
708
call $nop_params_and_results
709
f32.const 0 ;; assert the correct result
710
f32.eq
711
i32.eqz
712
if
713
unreachable
714
end
715
716
local.get 0 ;; decrement & break if necessary
717
i64.const -1
718
i64.add
719
local.tee 0
720
i64.const 0
721
i64.ne
722
br_if 0
723
end
724
)
725
)
726
727
(core instance $i (instantiate $m (with "" (instance
728
(export "nop" (func $core_nop))
729
(export "nop-params-and-results" (func $core_nop_params_and_results))
730
))))
731
732
(func (export "run-nop") (param "i" u64)
733
(canon lift (core func $i "run-nop"))
734
)
735
(func (export "run-nop-params-and-results") (param "i" u64)
736
(canon lift (core func $i "run-nop-params-and-results"))
737
)
738
)
739
"#;
740
741
for (engine, is_async) in engines() {
742
let mut store = Store::new(&engine, ());
743
let component = component::Component::new(&engine, module).unwrap();
744
745
bench_calls(
746
&mut c.benchmark_group(&format!("{}/no-hook", is_async.desc())),
747
&mut store,
748
&component,
749
is_async,
750
);
751
store.call_hook(|_, _| Ok(()));
752
bench_calls(
753
&mut c.benchmark_group(&format!("{}/hook-sync", is_async.desc())),
754
&mut store,
755
&component,
756
is_async,
757
);
758
}
759
760
// Given a `Store` will create various instances hooked up to different ways
761
// of defining host imports to benchmark their overhead.
762
fn bench_calls(
763
group: &mut BenchmarkGroup<'_, WallTime>,
764
store: &mut Store<()>,
765
component: &component::Component,
766
is_async: IsAsync,
767
) {
768
let engine = store.engine().clone();
769
let mut typed = component::Linker::new(&engine);
770
typed.root().func_wrap("nop", |_, ()| Ok(())).unwrap();
771
typed
772
.root()
773
.func_wrap("nop-params-and-results", |_, (x, y): (u32, u64)| {
774
assert_eq!(x, 0);
775
assert_eq!(y, 0);
776
Ok((0.0f32,))
777
})
778
.unwrap();
779
let instance = if is_async.use_async() {
780
run_await(typed.instantiate_async(&mut *store, &component)).unwrap()
781
} else {
782
typed.instantiate(&mut *store, &component).unwrap()
783
};
784
bench_instance(group, store, &instance, "typed", is_async);
785
786
let mut untyped = component::Linker::new(&engine);
787
untyped.root().func_new("nop", |_, _, _, _| Ok(())).unwrap();
788
untyped
789
.root()
790
.func_new("nop-params-and-results", |_caller, _ty, params, results| {
791
assert_eq!(params.len(), 2);
792
match params[0] {
793
component::Val::U32(0) => {}
794
_ => unreachable!(),
795
}
796
match params[1] {
797
component::Val::U64(0) => {}
798
_ => unreachable!(),
799
}
800
assert_eq!(results.len(), 1);
801
results[0] = component::Val::Float32(0.0);
802
Ok(())
803
})
804
.unwrap();
805
let instance = if is_async.use_async() {
806
run_await(untyped.instantiate_async(&mut *store, &component)).unwrap()
807
} else {
808
untyped.instantiate(&mut *store, &component).unwrap()
809
};
810
bench_instance(group, store, &instance, "untyped", is_async);
811
812
// Only define async host imports if allowed
813
if !is_async.use_async() {
814
return;
815
}
816
817
let mut typed = component::Linker::new(&engine);
818
typed
819
.root()
820
.func_wrap_async("nop", |caller, ()| {
821
Box::new(async {
822
drop(caller);
823
Ok(())
824
})
825
})
826
.unwrap();
827
typed
828
.root()
829
.func_wrap_async("nop-params-and-results", |_caller, (x, y): (u32, u64)| {
830
Box::new(async move {
831
assert_eq!(x, 0);
832
assert_eq!(y, 0);
833
Ok((0.0f32,))
834
})
835
})
836
.unwrap();
837
let instance = run_await(typed.instantiate_async(&mut *store, &component)).unwrap();
838
bench_instance(group, store, &instance, "async-typed", is_async);
839
}
840
841
// Given a specific instance executes all of the "runner functions"
842
fn bench_instance(
843
group: &mut BenchmarkGroup<'_, WallTime>,
844
store: &mut Store<()>,
845
instance: &component::Instance,
846
desc: &str,
847
is_async: IsAsync,
848
) {
849
group.bench_function(&format!("component - wasm-to-host - {desc} - nop"), |b| {
850
let run = instance
851
.get_typed_func::<(u64,), ()>(&mut *store, "run-nop")
852
.unwrap();
853
b.iter_custom(|iters| {
854
let start = Instant::now();
855
if is_async.use_async() {
856
run_await(run.call_async(&mut *store, (iters,))).unwrap();
857
} else {
858
run.call(&mut *store, (iters,)).unwrap();
859
}
860
start.elapsed()
861
})
862
});
863
group.bench_function(
864
&format!("component - wasm-to-host - {desc} - nop-params-and-results"),
865
|b| {
866
let run = instance
867
.get_typed_func::<(u64,), ()>(&mut *store, "run-nop-params-and-results")
868
.unwrap();
869
b.iter_custom(|iters| {
870
let start = Instant::now();
871
if is_async.use_async() {
872
run_await(run.call_async(&mut *store, (iters,))).unwrap();
873
} else {
874
run.call(&mut *store, (iters,)).unwrap();
875
}
876
start.elapsed()
877
})
878
},
879
);
880
}
881
}
882
}
883
884
mod indirect {
885
use super::*;
886
use std::time::Duration;
887
888
pub fn measure_execution_time(c: &mut Criterion) {
889
let _ = env_logger::try_init();
890
let mut group = c.benchmark_group("call-indirect");
891
for lazy in [true, false] {
892
// Note: the seemingly useless loop over a single `calls` value is
893
// just there to make it easy to play around with different numbers
894
// of calls.
895
for calls in [65536] {
896
group.throughput(criterion::Throughput::Elements(calls));
897
same_callee(&mut group, lazy, calls);
898
different_callees(&mut group, lazy, calls);
899
}
900
}
901
}
902
903
fn same_callee(group: &mut BenchmarkGroup<'_, WallTime>, lazy: bool, calls: u64) {
904
let name = format!(
905
"same-callee/table-init-{}/{calls}-calls",
906
if lazy { "lazy" } else { "strict" }
907
);
908
group.bench_function(name, |b| {
909
let mut config = Config::new();
910
config.table_lazy_init(lazy);
911
let engine = Engine::new(&config).unwrap();
912
913
let table_module = Module::new(
914
&engine,
915
r#"
916
(module
917
(func)
918
(table (export "table") 5 5 funcref)
919
(elem (table 0) (i32.const 0) func 0 0 0 0 0)
920
)
921
"#,
922
)
923
.unwrap();
924
925
let run_module = Module::new(
926
&engine,
927
r#"
928
(module
929
(type $ty (func))
930
(import "" "table" (table 0 funcref))
931
(func (export "run") (param $callee i32) (param $calls i32)
932
loop
933
(if (i32.eqz (local.get $calls))
934
(then (return)))
935
(local.set $calls (i32.sub (local.get $calls) (i32.const 1)))
936
(call_indirect (type $ty) (local.get $callee))
937
br 0
938
end
939
)
940
)
941
"#,
942
)
943
.unwrap();
944
945
b.iter_custom(move |iters| {
946
let mut total = Duration::from_millis(0);
947
948
for _ in 0..iters {
949
let mut store = Store::new(&engine, ());
950
951
let table_instance = Instance::new(&mut store, &table_module, &[]).unwrap();
952
let table = table_instance.get_table(&mut store, "table").unwrap();
953
954
let run_instance =
955
Instance::new(&mut store, &run_module, &[table.into()]).unwrap();
956
let run = run_instance
957
.get_typed_func::<(u32, u32), ()>(&mut store, "run")
958
.unwrap();
959
960
let start = Instant::now();
961
let result = run.call(&mut store, (0, calls.try_into().unwrap()));
962
total += start.elapsed();
963
964
result.unwrap();
965
}
966
967
total
968
});
969
});
970
}
971
972
fn different_callees(group: &mut BenchmarkGroup<'_, WallTime>, lazy: bool, calls: u64) {
973
let name = format!(
974
"different-callees/table-init-{}/{calls}-calls",
975
if lazy { "lazy" } else { "strict" }
976
);
977
group.bench_function(name, |b| {
978
let mut config = Config::new();
979
config.table_lazy_init(lazy);
980
let engine = Engine::new(&config).unwrap();
981
982
let mut table_wat = format!(
983
"
984
(module
985
(func)
986
(table (export \"table\") {calls} {calls} funcref)
987
(elem (table 0) (i32.const 0) func"
988
);
989
for _ in 0..calls {
990
table_wat.push_str(" 0");
991
}
992
table_wat.push_str("))");
993
let table_module = Module::new(&engine, &table_wat).unwrap();
994
995
let run_module = Module::new(
996
&engine,
997
r#"
998
(module
999
(type $ty (func))
1000
(import "" "table" (table 0 funcref))
1001
(func (export "run") (param $callee i32) (param $calls i32)
1002
loop
1003
(if (i32.eqz (local.get $calls))
1004
(then (return)))
1005
(local.set $calls (i32.sub (local.get $calls) (i32.const 1)))
1006
1007
(call_indirect (type $ty) (local.get $callee))
1008
(local.set $callee (i32.add (local.get $callee) (i32.const 1)))
1009
1010
br 0
1011
end
1012
)
1013
)
1014
"#,
1015
)
1016
.unwrap();
1017
1018
b.iter_custom(move |iters| {
1019
let mut total = Duration::from_millis(0);
1020
1021
for _ in 0..iters {
1022
let mut store = Store::new(&engine, ());
1023
1024
let table_instance = Instance::new(&mut store, &table_module, &[]).unwrap();
1025
let table = table_instance.get_table(&mut store, "table").unwrap();
1026
1027
let run_instance =
1028
Instance::new(&mut store, &run_module, &[table.into()]).unwrap();
1029
let run = run_instance
1030
.get_typed_func::<(u32, u32), ()>(&mut store, "run")
1031
.unwrap();
1032
1033
let start = Instant::now();
1034
let result = run.call(&mut store, (0, calls.try_into().unwrap()));
1035
total += start.elapsed();
1036
1037
result.unwrap();
1038
}
1039
1040
total
1041
});
1042
});
1043
}
1044
}
1045
1046