Can anyone explain why awaiting a ValueTask
method has about 4x overhead compared to awaiting a Task
method in this micro-benchmark?
It's allocating less, as expected, but the bigger overhead is unexpected.
I thought awaiting a ValueTask
would be faster or same as awaiting a Task
method.
| Method | Mean | Error | StdDev | Min | Max | Median | Allocated |
|--------------- |---------:|----------:|----------:|---------:|---------:|---------:|----------:|
| AsyncTask | 1.905 ms | 0.5159 ms | 0.0283 ms | 1.881 ms | 1.936 ms | 1.898 ms | 73 B |
| AsyncValueTask | 8.089 ms | 1.1259 ms | 0.0617 ms | 8.018 ms | 8.132 ms | 8.117 ms | 9 B |
[ShortRunJob]
[MinColumn, MaxColumn, MeanColumn, MedianColumn]
[MemoryDiagnoser]
[MarkdownExporter]
public class AsyncMethodCallsBench
{
[Benchmark]
public async Task<int> AsyncTask()
{
var sum = 0;
for (int i = 0; i < 1_000_000; i++)
{
sum += await Foo.AsyncTaskMethod(1);
}
return sum;
}
[Benchmark]
public async ValueTask<int> AsyncValueTask()
{
var sum = 0;
for (int i = 0; i < 1_000_000; i++)
{
sum += await Foo.AsyncValueTaskMethod(1);
}
return sum;
}
}
public class Foo
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static Task<int> AsyncTaskMethod(int arg) => Task.FromResult<int>(arg * 2);
[MethodImpl(MethodImplOptions.NoInlining)]
public static ValueTask<int> AsyncValueTaskMethod(int arg) => ValueTask.FromResult<int>(arg * 2);
}
EDIT:
Apparently Task.FromResult()
uses cache for some primitive values. If I pass i
instead of 1
for an argument, I get following results:
| Method | Mean | Error | StdDev | Min | Max | Median | Gen0 | Allocated |
|--------------- |---------:|----------:|----------:|---------:|---------:|---------:|----------:|-----------:|
| AsyncTask | 3.736 ms | 0.2676 ms | 0.0147 ms | 3.721 ms | 3.750 ms | 3.736 ms | 4300.7813 | 71999714 B |
| AsyncValueTask | 8.151 ms | 1.0713 ms | 0.0587 ms | 8.108 ms | 8.218 ms | 8.126 ms | - | 9 B |
Task
version now allocates significantly more, but is still 2x faster than ValueTask
method.