我终于得到了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<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;
}
}
}
有什么想法?我在做异步/等待错误吗?我很乐意尝试任何建议。
答案 0 :(得分:25)
将“异步”与“并行化”分开是很重要的。 await
可以帮助您更轻松地编写异步代码。并行运行的代码可能(或可能不)涉及异步,异步代码可能会或可能不会并行运行。
await
的任何内容都不是为了让并行代码更快。 await
的目的是使编写异步代码更容易,同时最大限度地减少负面的性能影响。使用await
永远不会比正确编写的非等待异步代码更快(尽管因为用await
编写正确的代码更容易,它有时会更快,因为程序员无法编写异步代码正确无需等待,或者不愿意花时间这样做。如果非同步代码编写得很好,它的性能也会比await
代码好一点,如果不是更好的话。
C#确实支持专门用于并行化,但它并不是专门用于await
。任务并行库(TPL)以及并行LINQ(PLINQ)有几种非常有效的代码并行化方法,通常比天真的线程实现更有效。
在您的情况下,使用PLINQ的有效实现可能是这样的:
public static int Sum(int[] array)
{
return array.AsParallel().Sum();
}
请注意,这将有效地将输入序列划分为将并行运行的块;它将负责确定块的适当大小和并发工作器的数量,并且它将适当地聚合那些正确同步的庄园中的工作者的结果,以确保正确的结果(与您的线程示例不同)并且高效(意味着它不会完全序列化所有聚合)。
答案 1 :(得分:11)
async
不适用于重型并行计算。您可以使用Task.Run
和Task.WhenAll
进行基本的并行工作,但任何严肃的并行工作都应该使用任务并行库(例如Parallel
)来完成。客户端的异步代码是响应性,而不是并行处理。
一种常见的方法是使用Parallel
进行并行工作,然后将其包装在Task.Run
中并使用await
来保留UI响应。
答案 2 :(得分:8)
您的基准测试存在一些缺陷:
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个线程。
答案 3 :(得分:4)
快速查看,结果是预期的:您的异步和只使用一个线程,而异步等待它完成,所以它比多线程总和慢。
如果您在完成工作时还有其他事情需要完成,那么您将使用异步。因此,这对于任何速度/响应改进都不是正确的测试。