Skip to content

Commit 9abad31

Browse files
committed
Benchmarking Async function & Mocks
* Adding tips and tricks for running benchmarks on async function to reduce the overhead of the async runtime * Tips for creating mocks for async trait to reduce the overhead for async runtime too
1 parent c192796 commit 9abad31

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

developer/src/performance/benchmarking.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,99 @@
3232
* `Iai-Callgrind` doesn't have currently a lot of recognition and support from Rust community compared to `Criterion`, however they have great documentation and the library is active development.
3333

3434

35+
## Benchmarking Asynchronous Functions
3536

37+
Asynchronous functions run within an async runtime, which can introduce overhead, especially when benchmarking small, simple functions (e.g., using mocks).
38+
39+
[Criterion](https://github.com/bheisler/criterion.rs) supports asynchronous benchmarking and allows users to configure the runtime. It also provides settings that can reduce the overhead introduced by the async runtime.
40+
41+
### Configuring Criterion with Async Runtimes
42+
43+
To minimize noise from the runtime, consider the following:
44+
45+
#### Use a New Runtime Instance for Each Iteration
46+
47+
Asynchronous runtimes configure themselves during initialization based on system state, potentially leading to runtime overhead accumulating in one direction. Using a new runtime for each iteration helps distribute this overhead.
48+
49+
Example using Tokio:
50+
51+
```rust
52+
// Using one runtime for all iterations leads to consistent overhead.
53+
let runner = tokio::runtime::Runtime::new().unwrap();
54+
bencher
55+
.to_async(&runner)
56+
.iter(|bencher| ...);
57+
58+
// Using a new runtime per iteration distributes the overhead.
59+
bencher
60+
.to_async(tokio::runtime::Runtime::new().unwrap())
61+
.iter(|bencher| ...);
62+
63+
```
64+
65+
#### Increase Warm-up Time
66+
67+
Allow the system to stabilize by increasing the warm-up time. This ensures that faulty runtime configurations are minimized when the system is cold.
68+
69+
#### Additional Configurations
70+
71+
- **Increase sample size and measurement time**: Reduces noise and outliers.
72+
- **Lower significance level**: Helps with noisy benchmarks to reduce false positives changes.
73+
- **Raise noise threshold**: Reduces false positives in performance changes.
74+
75+
#### Example of the Configuring:
76+
77+
```rust
78+
criterion_group! {
79+
...
80+
config = Criterion::default()
81+
// Warm-up time allows stable spawning of multiple async runtimes.
82+
.warm_up_time(Duration::from_secs(10))
83+
// Increased measurement time and sample size reduce noise.
84+
.measurement_time(Duration::from_secs(20))
85+
.sample_size(200)
86+
// Settings to reduce noise in the results.
87+
.significance_level(0.01)
88+
.noise_threshold(0.03);
89+
...
90+
}
91+
```
92+
93+
### Mocking Async Traits
94+
95+
When benchmarking generic functions with async traits, avoid calling `Future::poll()` on mocks to reduce async overhead. Achieve this by using a non-async inner function, marked as `#[inline(never)]`, within an always-inlined async function. This ensures the non-async function is used without inlining it to mimic the actual trait implementation.
96+
97+
Example:
98+
99+
```rust
100+
trait FooTrait {
101+
async fn bar(&self) -> usize;
102+
}
103+
104+
struct MockFoo;
105+
106+
impl FooTrait for MockFoo {
107+
#[inline(always)]
108+
async fn bar(&self) -> usize {
109+
#[inline(never)]
110+
fn inner_bar() -> usize {
111+
black_box(0)
112+
}
113+
114+
inner_bar()
115+
}
116+
}
117+
118+
```
119+
This avoids calling `Future::poll()` when invoking the async function:
120+
121+
```rust
122+
async some_fun(foo: FooTrait) {
123+
// The call can be made without awaiting.
124+
let val = foo.bar().await;
125+
126+
// `MockFoo::bar()` inlines, avoiding future polling, since `inner_bar()` isn't async.
127+
let val = inner_bar();
128+
}
129+
130+
```

0 commit comments

Comments
 (0)