为什么等待异步这么慢?(Why is await async so slow?)

2019-07-28 15:24发布

我终于得到了VS2012,得到了一个简单的演示起来,工作有检查的异步潜在的性能提升和等待,但我失望的是慢! 它可能我做错了什么,但也许你能帮助我。 (我还添加了一个简单的解决方案螺纹,并且运行正常更快)

我的代码使用一个类来总结一个数组,根据您的系统(-1)矿山有4个内核上的内核数量,所以我看到了有关线程2倍加速(2.5线程),但2X放慢了同样的事情,但与异步/等待。

验证码:(请注意,您将需要添加的参考System.Management获得核心探测器工作)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Management;
using System.Diagnostics;

namespace AsyncSum
{
    class Program
    {
        static string Results = "";

        static void Main(string[] args)
        {
            Task t = Run();
            t.Wait();

            Console.WriteLine(Results);
            Console.ReadKey();
        }

        static async Task Run()
        {
            Random random = new Random();

            int[] huge = new int[1000000];

            for (int i = 0; i < huge.Length; i++)
            {
                huge[i] = random.Next(2);
            }

            ArraySum summer = new ArraySum(huge);

            Stopwatch sw = new Stopwatch();

            sw.Restart();
            long tSum = summer.Sum();
            for (int i = 0; i < 100; i++)
            {
                tSum = summer.Sum();
            }
            long tticks = sw.ElapsedTicks / 100;

            long aSum = await summer.SumAsync();
            sw.Restart();
            for (int i = 0; i < 100; i++)
            {
                aSum = await summer.SumAsync();
            }
            long aticks = sw.ElapsedTicks / 100;

            long dSum = summer.SumThreaded();
            sw.Restart();
            for (int i = 0; i < 100; i++)
            {
                dSum = summer.SumThreaded();
            }
            long dticks = sw.ElapsedTicks / 100;


            long pSum = summer.SumParallel();
            sw.Restart();
            for (int i = 0; i < 100; i++)
            {
                pSum = summer.SumParallel();
            }
            long pticks = sw.ElapsedTicks / 100;

            Program.Results += String.Format("Regular Sum: {0} in {1} ticks\n", tSum, tticks);
            Program.Results += String.Format("Async Sum: {0} in {1} ticks\n", aSum, aticks);
            Program.Results += String.Format("Threaded Sum: {0} in {1} ticks\n", dSum, dticks);
            Program.Results += String.Format("Parallel Sum: {0} in {1} ticks\n", pSum, pticks);
        }
    }

    class ArraySum
    {
        int[] Data;
        int ChunkSize = 1000;
        int cores = 1;


        public ArraySum(int[] data)
        {
            Data = data;

            cores = 0;
            foreach (var item in new System.Management.ManagementObjectSearcher("Select * from Win32_Processor").Get())
            {
                cores += int.Parse(item["NumberOfCores"].ToString());
            }
            cores--;
            if (cores < 1) cores = 1;

            ChunkSize = Data.Length / cores + 1;
        }

        public long Sum()
        {
            long sum = 0;
            for (int i = 0; i < Data.Length; i++)
            {
                sum += Data[i];
            }
            return sum;
        }

        public async Task<long> SumAsync()
        {
            Task<long>[] psums = new Task<long>[cores];
            for (int i = 0; i < psums.Length; i++)
            {
                int start = i * ChunkSize;
                int end = start + ChunkSize;

                psums[i] = Task.Run<long>(() =>
                {
                    long asum = 0;
                    for (int a = start; a < end && a < Data.Length; a++)
                    {
                        asum += Data[a];
                    }
                    return asum;
                });
            }

            long sum = 0;
            for (int i = 0; i < psums.Length; i++)
            {
                sum += await psums[i];
            }

            return sum;
        }

        public long SumThreaded()
        {
            long sum = 0;
            Thread[] threads = new Thread[cores];
            long[] buckets = new long[cores];
            for (int i = 0; i < cores; i++)
            {
                int start = i * ChunkSize;
                int end = start + ChunkSize;
                int bucket = i;
                threads[i] = new Thread(new ThreadStart(() =>
                {
                    long asum = 0;
                    for (int a = start; a < end && a < Data.Length; a++)
                    {
                        asum += Data[a];
                    }
                    buckets[bucket] = asum;
                }));
                threads[i].Start();
            }

            for (int i = 0; i < cores; i++)
            {
                threads[i].Join();
                sum += buckets[i];
            }

            return sum;
        }

        public long SumParallel()
        {
            long sum = 0;
            long[] buckets = new long[cores];
            ParallelLoopResult lr = Parallel.For(0, cores, new Action<int>((i) =>
            {
                int start = i * ChunkSize;
                int end = start + ChunkSize;
                int bucket = i;
                long asum = 0;
                for (int a = start; a < end && a < Data.Length; a++)
                {
                    asum += Data[a];
                }
                buckets[bucket] = asum;
            }));

            for (int i = 0; i < cores; i++)
            {
                sum += buckets[i];
            }

            return sum;
        }
    }
}

有什么想法吗? 我做异步/等待错了吗? 我会很乐意去尝试任何建议。

Answer 1:

您的基准有几个缺陷:

  • 你是定时的第一次运行,它包括初始化时间(装载class Task ,JIT编译等)
  • 您正在使用DateTime.Now ,这是在毫秒范围计时太不精确。 你需要使用StopWatch

有了固定的这两个问题; 我得到以下基准测试结果:

Regular Sum:  499946 in 00:00:00.0047378
Async Sum:    499946 in 00:00:00.0016994
Threaded Sum: 499946 in 00:00:00.0026898

异步体现出来的最快的解决方案,以小于2ms。

这是下一个问题:什么时间以最快的速度为2ms是极不可靠; 如果一些其他进程正在使用的后台CPU你的线程可以得到暂停都比较长。 你应该平均值几千基准运行的结果。

此外,这是怎么回事你的核心检测的数字? 我的四核使用的333334块大小只允许3个线程来运行。



Answer 2:

重要的是要分开“并行”,“不同步”是很重要的。 await是有帮助使编写异步代码更容易。 在平行运行5(或可能不)包括异步即异步可以或可以不以并行方式运行的代码,和代码。

无事await旨在使并行代码的速度更快。 的目的await是使编写异步代码更容易 ,同时尽量减少负面影响性能。 使用await永远不会快于正确书写不等待异步代码(尽管因为编写正确的代码await很容易,它有时会更快,因为程序员不能够编写异步代码不正确的的await,或ISN “T愿意把时间这样做。如果非异步代码写得好它会执行一下为好,如果不是一点点好,比await代码。

C#中确实有支持专为并行化,它只是没有明确不过await 。 任务并行库(TPL)以及并行LINQ(PLINQ)具有并行代码一般比幼稚螺纹实现更有效的几个非常有效的手段。

在你的情况下,使用PLINQ有效的实现可能是这样的:

public static int Sum(int[] array)
{
    return array.AsParallel().Sum();
}

注意,这将照顾有效地将所述输入序列分类为将并行运行块; 这将需要确定块的适当规模,和并发职工人数的照顾,并会适当地聚合这些工人的结果,既正确同步,以确保正确的结果(不像你的螺纹例)的庄园和高效(这意味着它不会完全序列化所有聚合)。



Answer 3:

async不用于重型并行计算。 你可以用做基本的并行工作Task.RunTask.WhenAll ,但任何严重的并行工作,应使用任务并行库(例如,进行Parallel )。 在客户端异步代码为约响应性 ,不平行的处理

一种常见的方法是使用Parallel并行工作,然后在把它包装Task.Run和使用await它来保持UI响应。



Answer 4:

在快看,预期的结果:你的异步总和只使用一个线程,当你异步地等待它完成,因此它比多线程和慢。

你会使用异步如果你有别的事,而它做的工作来完成。 所以,这不会是任何速度/响应的改进正确的测试。



文章来源: Why is await async so slow?