This benchmark compares the performance impact of using async/await with Task and ValueTask in C#. It focuses on scenarios where the asynchronous operation is trivial and completes immediately, such as returning a constant value.
The goal is to understand the overhead introduced by async/await in cases where it may be unnecessary. For example, returning a cached value or a precomputed result in an async method.
| Method Name | Description |
|---|---|
GetStringSync |
Returns a string synchronously. |
GetStringWithoutAsync |
Returns Task<string> without using async or await. |
GetStringWithAsync |
Uses async + await, wrapping Task.FromResult. |
GetStringWithoutAsyncValue |
Returns ValueTask<string> without using async or await. |
GetStringWithAsyncValue |
Uses async + await, wrapping ValueTask.FromResult. |
- Avoid unnecessary
async/awaitif the method doesn't actually await anything. - Using
Task.FromResultorValueTask.FromResultdirectly is more efficient, as it avoids state machine generation and heap allocations. ValueTask<T>can be more efficient thanTask<T>for frequently synchronous results.- In real-world I/O-bound scenarios, this overhead is negligible, but in performance-critical code paths (e.g., high-frequency methods, microservices, low-latency systems), it can add up.
private Task<string> GetStringWithoutAsyncCore()
{
return Task.FromResult("I'm great developer.");
}
private async Task<string> GetStringWithAsyncCore()
{
return await Task.FromResult("I'm great developer.");
}| Method | Mean | Error | StdDev | Gen0 | Allocated |
|---|---|---|---|---|---|
| GetStringWithAsync | 30.4626 ns | 0.7727 ns | 2.2295 ns | 0.0344 | 216 B |
| GetStringWithoutAsync | 17.3824 ns | 0.3938 ns | 0.6471 ns | 0.0229 | 144 B |
| GetStringWithoutAsyncValue | 10.6441 ns | 0.0675 ns | 0.0598 ns | - | - |
| GetStringWithAsyncValue | 17.6272 ns | 0.1637 ns | 0.1278 ns | - | - |
| GetStringSync | 0.0625 ns | 0.0202 ns | 0.0189 ns | - | - |