为什么await async这么慢?
本文关键字:await async 为什么 | 更新日期: 2023-09-27 18:06:04
我终于得到了VS2012,并得到了一个简单的演示,并开始工作,以检查async和await的潜在性能提升,但令我沮丧的是它更慢!我可能做错了什么,但也许你能帮我一把。(我还添加了一个简单的线程解决方案,运行速度比预期的要快)
我的代码使用一个类来求和数组,基于系统上的核心数量(-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<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;
}
}
}
任何想法吗?我做async/await错误吗?我很乐意尝试任何建议。
将"异步"与"并行化"分开是很重要的。await
的存在是为了帮助更容易地编写异步代码。并行运行的代码可能包含(也可能不包含)异步,而异步的代码可能包含也可能不包含并行运行。
await
不是为了使并行代码更快而设计的。await
的目的是使编写异步代码更容易,同时尽量减少对性能的负面影响。使用await
不会比正确编写的非等待异步代码更快(尽管因为使用await
编写正确的代码更容易,但有时会更快,因为程序员无法在没有await的情况下正确编写异步代码,或者不愿意花时间这样做。如果非异步代码写得好,它的性能将与await
代码一样好,如果不是稍微好一点的话。
await
。任务并行库(TPL)和并行LINQ (PLINQ)有几种非常有效的并行代码方法,通常比简单的线程实现更有效。
在您的情况下,使用PLINQ的有效实现可能是这样的:
public static int Sum(int[] array)
{
return array.AsParallel().Sum();
}
注意,这将有效地将输入序列划分为将并行运行的块;它将负责确定块的适当大小和并发工作线程的数量,并将这些工作线程的结果适当地聚合在一个适当同步的庄园中,以确保正确的结果(与您的线程示例不同)和高效(意味着它不会完全序列化所有聚合)。
async
不适用于繁重的并行计算。您可以使用Task.Run
和Task.WhenAll
来完成基本的并行工作,但是任何重要的并行工作都应该使用任务并行库(例如Parallel
)来完成。客户端的异步代码是关于响应,而不是并行处理。
一种常见的方法是使用Parallel
来处理并行的工作,然后将其包装在Task.Run
中,并使用await
来保持UI响应。
您的基准测试有几个缺陷:
- 你正在计时第一次运行,其中包括初始化时间(加载
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现在是最快的解决方案,只需不到2ms。
这是下一个问题:像2毫秒这样快的时间是非常不可靠的;如果有其他进程在后台使用CPU,你的线程可能会暂停更长时间。您应该对几千次基准测试的结果取平均值。
还有,你的核心检测数量是怎么回事?我的四核使用的块大小为333334,它只允许3个线程运行。
快速查看一下,结果是预期的:您的异步求和只使用一个线程,而您异步等待它完成,因此它比多线程求和慢。
你会使用async,以防你有其他事情要完成,而它正在做它的工作。因此,这不是测试任何速度/响应改进的正确方法。