You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

微基准测试中Async ValueTask为何比Async Task性能更低?

为什么等待ValueTask的开销是Task的4倍?

我做了一个微基准测试,发现等待ValueTask方法的开销大约是等待Task方法的4倍。虽然ValueTask的内存分配符合预期(更少),但性能开销更大完全出乎意料——我原本以为等待ValueTask的速度会和Task相当甚至更快。

第一次测试(参数固定为1)的结果:

MethodMeanErrorStdDevMinMaxMedianAllocated
AsyncTask1.905 ms0.5159 ms0.0283 ms1.881 ms1.936 ms1.898 ms73 B
AsyncValueTask8.089 ms1.1259 ms0.0617 ms8.018 ms8.132 ms8.117 ms9 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);
}

编辑补充:
后来发现Task.FromResult()会缓存部分原始值,于是把参数改为循环变量i,测试结果如下:

MethodMeanErrorStdDevMinMaxMedianGen0Allocated
AsyncTask3.736 ms0.2676 ms0.0147 ms3.721 ms3.750 ms3.736 ms4300.781371999714 B
AsyncValueTask8.151 ms1.0713 ms0.0587 ms8.108 ms8.218 ms8.126 ms-9 B

此时Task版本的内存分配大幅增加,但速度仍比ValueTask版本快2倍。


核心原因:同步场景下ValueTask的await路径更复杂

1. Task.FromResult的特殊优化

Task.FromResult<T>返回的是已完成的缓存Task实例(当T为值类型且结果常见时,CLR会复用实例),即使改成参数i每次创建新Task,await已完成Task的路径在.NET里有高度优化——JIT会识别到await的是已完成Task,直接同步执行后续代码,几乎没有额外开销。

2. ValueTask的await额外开销

对于ValueTask.FromResult<T>返回的已完成ValueTask,await它的路径比Task要复杂:

  • ValueTask是结构体,await时需要先检查它的IsCompleted状态,然后调用GetAwaiter(),这个过程涉及结构体的拷贝和状态判断;
  • 相比之下,Task的await路径因为被JIT深度优化,对于已完成的Task,编译器会生成更直接的代码,跳过很多状态检查步骤;
  • 另外,ValueTask的awaiter没有Task的awaiter那么多的JIT优化,尤其是在同步完成的场景下,ValueTask的处理逻辑更多。

3. 循环放大了差异

测试是循环100万次,每次的微小差异会被放大。Task的同步await路径几乎是“零开销”,而ValueTask每次都要多做几个步骤,累计下来就形成了数倍的性能差距。

什么时候用ValueTask才合适?

ValueTask的设计目标是减少异步操作成功完成时的内存分配,但它的优势体现在真正异步的场景(比如I/O操作):

  • 当操作大概率会异步完成时,ValueTask避免了分配Task实例的开销;
  • 但在同步完成的高频场景下,Task的JIT优化带来的性能收益会超过ValueTask的内存优势,此时用Task反而更快。

内容的提问来源于stack exchange,提问作者zigzag

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 05:21:57