Skip to content

Commit 4dd638c

Browse files
authored
Add support for benchmarking async functions (#4823)
1 parent 3f227bb commit 4dd638c

File tree

12 files changed

+236
-27
lines changed

12 files changed

+236
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
* Added benchmark support to `wasm-bindgen-test`.
3131
[#4812](https://github.com/wasm-bindgen/wasm-bindgen/pull/4812)
32+
[#4823](https://github.com/wasm-bindgen/wasm-bindgen/pull/4823)
3233

3334
### Fixed
3435

benches/js_value_from.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use wasm_bindgen_test::{wasm_bindgen_bench, Criterion};
33

44
#[wasm_bindgen_bench]
55
fn bench_js_value_from(c: &mut Criterion) {
6-
c.bench_function("bench_from_str", |b| {
6+
c.bench_function("bench_js_value_from_str", |b| {
77
b.iter(|| JsValue::from_str("42"));
88
});
99
}

crates/cli/src/wasm_bindgen_test_runner.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -361,10 +361,14 @@ fn rmain(cli: Cli) -> anyhow::Result<()> {
361361
PathBuf::from(path)
362362
} else {
363363
// such as `js-sys/target/wbg_benchmark.json`
364-
env::current_dir()
364+
let path = env::current_dir()
365365
.context("Failed to get current dir")?
366-
.join("target")
367-
.join("wbg_benchmark.json")
366+
.join("target");
367+
// crates in the workspace that do not have a target dir.
368+
if cli.bench {
369+
fs::create_dir_all(&path)?;
370+
}
371+
path.join("wbg_benchmark.json")
368372
};
369373

370374
// The debug here means adding some assertions and some error messages to the generated js

crates/cli/tests/reference/js-namespace-export.bg.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ function concat(a, b) {
212212
* @param {string} s
213213
* @returns {string}
214214
*/
215-
function uppercase2(s) {
215+
function uppercase(s) {
216216
let deferred2_0;
217217
let deferred2_1;
218218
try {
@@ -231,7 +231,7 @@ const _default = {};
231231
_default.Counter = Counter;
232232
_default.concat = concat;
233233
_default.uppercase = {};
234-
_default.uppercase.uppercase = uppercase2;
234+
_default.uppercase.uppercase = uppercase;
235235
export default _default;
236236

237237
/**
@@ -402,7 +402,7 @@ types.http.HttpStatus = HttpStatus;
402402
* @param {string} s
403403
* @returns {string}
404404
*/
405-
function uppercase(s) {
405+
function uppercase2(s) {
406406
let deferred2_0;
407407
let deferred2_1;
408408
try {
@@ -419,7 +419,7 @@ function uppercase(s) {
419419

420420
export const utils = {};
421421
utils.string = {};
422-
utils.string.uppercase = uppercase;
422+
utils.string.uppercase = uppercase2;
423423

424424
export function __wbg___wbindgen_throw_b855445ff6a94295(arg0, arg1) {
425425
throw new Error(getStringFromWasm0(arg0, arg1));

crates/cli/tests/reference/js-namespace-export.d.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,13 @@ declare class Counter {
2525

2626
declare function concat(a: string, b: string): string;
2727

28-
declare function uppercase2(s: string): string;
28+
declare function uppercase(s: string): string;
2929

3030
declare let _default: {
3131
Counter: typeof Counter,
3232
concat: typeof concat,
3333
uppercase: {
34-
uppercase: typeof uppercase2,
34+
uppercase: typeof uppercase,
3535
},
3636
};
3737
export default _default;
@@ -93,10 +93,10 @@ export let types: {
9393
},
9494
};
9595

96-
declare function uppercase(s: string): string;
96+
declare function uppercase2(s: string): string;
9797

9898
export let utils: {
9999
string: {
100-
uppercase: typeof uppercase,
100+
uppercase: typeof uppercase2,
101101
},
102102
};

crates/futures/benches/promise.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use js_sys::Promise;
2+
use wasm_bindgen::JsValue;
3+
use wasm_bindgen_futures::JsFuture;
4+
use wasm_bindgen_test::{wasm_bindgen_bench, Criterion};
5+
6+
#[wasm_bindgen_bench]
7+
async fn bench_promise(c: &mut Criterion) {
8+
c.bench_async_function("bench_promise_to_future", |b| {
9+
let f = b.iter_future(|| JsFuture::from(Promise::resolve(&JsValue::from(42))));
10+
Box::pin(f)
11+
})
12+
.await;
13+
}

crates/test/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ wasm-bindgen-futures = { path = '../futures', version = '=0.4.55', default-featu
2121
wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.3.55' }
2222

2323
# benchmark required start
24+
async-trait = "0.1.89"
2425
cast = "0.3"
2526
libm = "0.2.11"
2627
nu-ansi-term = { version = "0.50", default-features = false }

crates/test/src/rt/criterion/analysis.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use super::{baseline, compare, Criterion, SavedSample};
1515
use alloc::vec::Vec;
1616

1717
// Common analysis procedure
18-
pub(crate) fn common<M: Measurement>(
18+
pub(crate) async fn common<M: Measurement>(
1919
id: &BenchmarkId,
2020
routine: &mut dyn Routine<M>,
2121
config: &BenchmarkConfig,
@@ -24,7 +24,9 @@ pub(crate) fn common<M: Measurement>(
2424
criterion.report.benchmark_start(id);
2525

2626
let (sampling_mode, iters, times);
27-
let sample = routine.sample(&criterion.measurement, id, config, criterion);
27+
let sample = routine
28+
.sample(&criterion.measurement, id, config, criterion)
29+
.await;
2830
sampling_mode = sample.0;
2931
iters = sample.1;
3032
times = sample.2;

crates/test/src/rt/criterion/bencher.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use super::measurement::Measurement;
2+
use core::future::Future;
23
use core::hint::black_box;
34
use core::time::Duration;
45

@@ -64,4 +65,33 @@ impl<'a, M: Measurement> Bencher<'a, M> {
6465
self.value = end;
6566
self.elapsed_time = end;
6667
}
68+
69+
/// Times a `routine` by executing it many times and timing the total elapsed time.
70+
///
71+
/// Prefer this timing loop when `routine` returns a value that doesn't have a destructor.
72+
///
73+
/// # Timing model
74+
///
75+
/// Note that the `Bencher` also times the time required to destroy the output of `routine()`.
76+
/// Therefore prefer this timing loop when the runtime of `mem::drop(O)` is negligible compared
77+
/// to the runtime of the `routine`.
78+
///
79+
/// ```text
80+
/// elapsed = Instant::now + iters * (routine + mem::drop(O) + Range::next)
81+
/// ```
82+
#[inline(never)]
83+
pub async fn iter_future<O, R, Fut>(&mut self, mut routine: R)
84+
where
85+
R: FnMut() -> Fut,
86+
Fut: Future<Output = O>,
87+
{
88+
self.iterated = true;
89+
let start = self.measurement.start();
90+
for _ in 0..self.iters {
91+
black_box(routine().await);
92+
}
93+
let end = self.measurement.end(start);
94+
self.value = end;
95+
self.elapsed_time = end;
96+
}
6797
}

crates/test/src/rt/criterion/mod.rs

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,15 @@ mod report;
3636
mod routine;
3737
mod stats;
3838

39+
use core::future::Future;
40+
use core::pin::Pin;
41+
use core::ptr;
42+
use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
3943
use core::time::Duration;
4044
use libm::{ceil, sqrt};
4145
use serde::{Deserialize, Serialize};
4246

47+
use alloc::boxed::Box;
4348
use alloc::vec;
4449
use alloc::vec::Vec;
4550
use benchmark::BenchmarkConfig;
@@ -249,6 +254,7 @@ impl<M: Measurement> Criterion<M> {
249254
self
250255
}
251256
}
257+
252258
impl<M> Criterion<M>
253259
where
254260
M: Measurement + 'static,
@@ -264,7 +270,7 @@ where
264270
/// fn bench(c: &mut Criterion) {
265271
/// // Setup (construct data, allocate memory, etc)
266272
/// c.bench_function(
267-
/// "function_name",
273+
/// "bench desc",
268274
/// |b| b.iter(|| {
269275
/// // Code to benchmark goes here
270276
/// }),
@@ -274,9 +280,69 @@ where
274280
pub fn bench_function<F>(&mut self, desc: &str, f: F) -> &mut Criterion<M>
275281
where
276282
F: FnMut(&mut Bencher<'_, M>),
283+
{
284+
const NOOP: RawWaker = {
285+
const VTABLE: RawWakerVTable = RawWakerVTable::new(
286+
// Cloning just returns a new no-op raw waker
287+
|_| NOOP,
288+
// `wake` does nothing
289+
|_| {},
290+
// `wake_by_ref` does nothing
291+
|_| {},
292+
// Dropping does nothing as we don't allocate anything
293+
|_| {},
294+
);
295+
RawWaker::new(ptr::null(), &VTABLE)
296+
};
297+
298+
// bench_function never be pending
299+
fn block_on(f: impl Future<Output = ()>) {
300+
let waker = unsafe { Waker::from_raw(NOOP) };
301+
let mut ctx = Context::from_waker(&waker);
302+
match core::pin::pin!(f).poll(&mut ctx) {
303+
Poll::Ready(_) => (),
304+
// sync functions not be pending
305+
Poll::Pending => unreachable!(),
306+
}
307+
}
308+
309+
let id = report::BenchmarkId::new(desc.into());
310+
block_on(analysis::common(
311+
&id,
312+
&mut routine::Function::new(f),
313+
&self.config,
314+
self,
315+
));
316+
317+
self
318+
}
319+
320+
/// Benchmarks a future.
321+
///
322+
/// # Example
323+
///
324+
/// ```rust
325+
/// use wasm_bindgen_test::{Criterion, wasm_bindgen_bench};
326+
///
327+
/// #[wasm_bindgen_bench]
328+
/// async fn bench(c: &mut Criterion) {
329+
/// // Setup (construct data, allocate memory, etc)
330+
/// c.bench_async_function(
331+
/// "bench desc",
332+
/// Box::pin(
333+
/// b.iter_future(|| async {
334+
/// // Code to benchmark goes here
335+
/// })
336+
/// )
337+
/// ).await;
338+
/// }
339+
/// ```
340+
pub async fn bench_async_function<F>(&mut self, desc: &str, f: F) -> &mut Criterion<M>
341+
where
342+
for<'b> F: FnMut(&'b mut Bencher<'_, M>) -> Pin<Box<dyn Future<Output = ()> + 'b>>,
277343
{
278344
let id = report::BenchmarkId::new(desc.into());
279-
analysis::common(&id, &mut routine::Function::new(f), &self.config, self);
345+
analysis::common(&id, &mut routine::AsyncFunction::new(f), &self.config, self).await;
280346
self
281347
}
282348
}

0 commit comments

Comments
 (0)