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