“synchronized”异步方法的开销是什么?

5

这里有一个方法返回任务的基准测试,但在内部是同步运行的。

class MainClass
{
    public static async Task<int> UsingAsyncModifier()
    {
        return 10;
    }

    public static Task<int> UsingTaskCompletionSource()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tcs.SetResult(10);
        return tcs.Task;
    }

    public static Task<int> UsingTaskFromResult()
    {
        return Task.FromResult(10);
    }

    public static void Main(string[] args)
    {
        DateTime t = DateTime.Now;
        const int repeat = 10000; // Results volatile while repeat grows.
        Console.WriteLine("Repeat {0} times.", repeat);

        int j = 0;
        for (int i = 0; i < repeat; i++)
        {
            j += UsingAsyncModifier().Result;
        }
        Console.WriteLine("UsingAsyncModifier: {0}", DateTime.Now - t);
        t = DateTime.Now;

        for (int i = 0; i < repeat; i++)
        {
            j += UsingTaskCompletionSource().Result;
        }
        Console.WriteLine("UsingTaskCompletionSource: {0}", DateTime.Now - t);
        t = DateTime.Now;

        for (int i = 0; i < repeat; i++)
        {
            j += UsingTaskFromResult().Result;
        }
        Console.WriteLine("UsingTaskFromResult: {0}", DateTime.Now - t);
    }
}

输出(重复10,000/100,000/1,000,000次):

Repeat 10000 times.
UsingAsyncModifier: 00:00:00.1043980
UsingTaskCompletionSource: 00:00:00.0095270
UsingTaskFromResult: 00:00:00.0089460

重复10,000次,UsingTaskFromResultUsingAsyncModifier 快10倍。

Repeat 100000 times.
UsingAsyncModifier: 00:00:00.1676000
UsingTaskCompletionSource: 00:00:00.0872020
UsingTaskFromResult: 00:00:00.0870180

重复操作 100,000 次,UsingTaskFromResult 比 UsingAsyncModifier 更快 2 倍。

Repeat 1000000 times.
UsingAsyncModifier: 00:00:00.8458490
UsingTaskCompletionSource: 00:00:00.8870980
UsingTaskFromResult: 00:00:00.9027320

重复1,000,000次,使用AsyncModifier比使用TaskFromResult稍微快一点。

我的想法是,async修饰符只是创建了一个已完成的任务,类似于Task.FromResult()。但是基准测试并没有证明我的想法。为什么?


8
高迭代次数下时间几乎相同的事实很可能意味着较低迭代中的差异是基于噪声、GC收集、上下文切换和基准测试框架中其他错误。此外,请注意,DateTime.Now实际上没有足够的精度来测量如此短的时间。对于执行如此快速的基准测试代码片段,非常困难。实际上,你的测试并没有传达任何有意义的信息;误差范围非常高,结果实际上毫无意义。 - Servy
值得注意的是,你的第一种方法非常简单,可能会被编译器优化掉。甚至有可能它会同步运行(我知道在WPF的Dispatcher类中,后台作业运行得非常快,以至于在异步请求返回之前,它看起来已经完成了)。正如@Servy所建议的那样,在这些条件下,你的测试并不真正具有意义。 - Dan Puzey
@DanPuzey,没有(或直到)await的异步方法会同步运行。但是,它不能在不返回任务来包装返回结果的情况下运行。我相信使用async的人正确理解了我所写的,而不必费心思如何以更长的方式写得不太含糊。 - Gennady Vanin Геннадий Ванин
1个回答

2

虽然我使用 DateTime 看到了类似的结果,但使用 Stopwatch 进行时间测量表明,使用 UsingAsyncModifier() 进行迭代需要 2 倍的时间(与使用 UsingTaskCompletionSource()UsingTaskFromResult() 相比,两者的持续时间相等),即使进行 1,000,000 次迭代。

以下是输出:

Repeat 1000000 times.
UsingAsyncModifier: 5458
UsingTaskCompletionSource: 2838
UsingTaskFromResult: 2556

使用 Stopwatch 来计时你的代码

class Program
{
     public static async Task<int> UsingAsyncModifier()
    {
        return 10;
    }

    public static Task<int> UsingTaskCompletionSource()
    {
        TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
        tcs.SetResult(10);
        return tcs.Task;
    }
    public static Task<int> UsingTaskFromResult()
    {
        return TaskEx.FromResult(10);
    }
    static void Main(string[] args)
    {
      //DateTime t = DateTime.Now;
      Stopwatch timer = new Stopwatch();
      const int repeat = 1000*1000; // Results volatile while repeat grows.
      Console.WriteLine("Repeat {0} times.", repeat);

        int j = 0;
        //DateTime t = DateTime.Now;
        timer.Start();
        for (int i = 0; i < repeat; i++)
        {
            j += UsingAsyncModifier().Result;
        }
        timer.Stop();
        Console.WriteLine("UsingAsyncModifier: {0}"
                          , timer.ElapsedMilliseconds);
        //t = DateTime.Now;
        timer.Reset();

        j = 0;

        timer.Start();
        for (int i = 0; i < repeat; i++)
        {
            j += UsingTaskCompletionSource().Result;
        }
        timer.Stop();
        Console.WriteLine("UsingTaskCompletionSource: {0}"
                           , timer.ElapsedMilliseconds);
        //t = DateTime.Now;
        timer.Reset();
        j = 0;
        timer.Start();
        for (int i = 0; i < repeat; i++)
        {
          j += UsingTaskFromResult().Result;
        }
        timer.Stop();
        Console.WriteLine("UsingTaskFromResult: {0}"
                          , timer.ElapsedMilliseconds);

        Console.ReadLine();
    }
}

在他的 "异步性能: 理解异步和等待的成本"文章中,Stephen Toub 解释道:

在处理同步代码时,空方法几乎是免费的。但是对于异步方法来说并非如此。

阅读文章以获取更多详细信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接