为什么等待异步这么慢?

我终于得到了VS2012并得到了一个简单的演示,并努力检查异步的潜在性能提升并等待,但令我沮丧的是它更慢! 它可能我做错了,但也许你可以帮助我。 (我还添加了一个简单的Threaded解决方案,并且按预期运行得更快)

我的代码使用一个类来根据系统中的内核数量对数组求和(-1)我有4个内核,所以我看到了大约2倍的加速(2.5个线程)用于线程,但是减少了2倍的速度同样的事情,但使用async / await。

代码:(注意,您需要添加对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 SumAsync() { Task[] psums = new Task[cores]; for (int i = 0; i < psums.Length; i++) { int start = i * ChunkSize; int end = start + ChunkSize; psums[i] = Task.Run(() => { 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  { 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((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; } } } 

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

您的基准测试有几个缺陷:

  • 你是第一次运行的计时,包括初始化时间(加载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 

Async现在成为最快的解决方案,耗时不到2毫秒。

这是下一个问题:时间快到2毫秒非常不可靠; 如果某个其他进程在后台使用CPU,则您的线程可以暂停更长时间。 您应该将结果平均在数千个基准运行中。

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

将“异步”与“并行化”分开是很重要的。 await有助于简化编写异步代码。 并行运行的代码可能(或可能不)涉及异步,并且异步的代码可能会或可能不会并行运行。

没有关于await任何内容旨在使并行代码更快。 await的目的是使编写异步代码更容易 ,同时最小化负面性能影响。 使用await永远不会比正确编写的非等待异步代码更快(尽管因为使用await编写正确的代码更容易,它有时会更快,因为程序员无法在没有等待的情况下正确编写异步代码,或者不是我不愿意花时间这样做。如果非同步代码编写得很好,它会比await代码更好地执行,如果不是更好。

C#确实有专门针对并行化的支持,它只是没有具体的await 。 任务并行库(TPL)以及并行LINQ(PLINQ)具有几种非常有效的并行化代码的方法,通常比天真的线程实现更有效。

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

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

请注意,这将有效地将输入序列划分为将并行运行的块; 它将负责确定块的适当大小和并发工作器的数量,并且它将适当地聚合那些正确同步的庄园中的工作者的结果,以确保正确的结果(与您的线程示例不同)并且高效(意味着它不会完全序列化所有聚合)。

async不适用于重型并行计算。 您可以使用Task.RunTask.WhenAll进行基本的并行工作,但任何严重的并行工作都应该使用任务并行库(例如, Parallel )完成。 客户端的异步代码是关于响应性 ,而不是并行处理

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

快速查看,结果是预期的:您的异步和只使用一个线程,而异步等待它完成,所以它比multithreading总和慢。

你可以使用异步,以防你在完成工作时完成其他任务。 因此,对于任何速度/响应改进而言,这都不是正确的测试。