I’m relatively new to async programming, and I’m exploring the benefits of transforming synchronous code into its async equivalent. To verify my assumptions and measure the performance improvements, I want to benchmark the same program running in both synchronous and asynchronous modes.
The idea is to achieve the following:
Synchronous Simulation with Async: Configure the executor to behave like a synchronous system. Specifically, I want to disable task switching at await points. If a future cannot progress, the executor should block until it completes—effectively enforcing a 1-to-1 relationship between async tasks and threads.
Benchmark Comparison: Measure the execution time of the same code in synchronous mode and in its standard async mode. I’d like to compare these two runs programmatically within a test and determine whether the async version performs better.
I’m wondering:
Does Tokio (or any other async runtime) support this kind of executor configuration? For instance, can I disable task dispatching on await points to enforce blocking behavior?
Is there a clean way to perform this kind of benchmark and validation inside a test? Ideally, the test would pass or fail depending on whether the async execution was faster than the synchronous one.
Any insights, suggestions, or ideas on how to approach this would be greatly appreciated!
The most strict way to do this is to use a single-task single-thread minimal executor, like pollster. Whenever you want to create a task, spawn a thread and call pollster::block_on(future) in that thread. It’s impossible for any task switching or task migration to happen, because pollster simply does not support it.
If you need Tokio’s IO operations, then you can do somewhat the same thing by using Handle::block_on(), but it’s a little unclear that it won't run any other tasks on the thread you call block_on() from. More strict would be to stick to pollster::block_on(), and use Handle::enter() to let the Tokio functions find the Tokio runtime for IO, but not use it for any task execution. However, that then is “unrealistic” because you lose the benefits of Tokio’s internal cooperation between its IO primitives and its executor.
Note that it is likely that, all else being equal, your async code run in this style will be somewhat slower than non-async code doing the same work would be, if it doesn't take any advantage of async facilities (not just lightweight tasks but also join, select, etc). The benefits of async come primarily when you do have many more tasks per thread, but they are not without overhead (some data is copied more times than it would otherwise be, and often lives in the heap instead of the stack), so your proposed benchmark will not be a totally “fair” comparison with the hypothetical of non-async code.