并行这个概念近来很火,.NET 4 中也引入了 TPL(任务并行库) 和 PLinq(并行Linq)来简化并行编程。
于是想体验下,便从最简单的并行求和开始,看看并行的效率如何。
我用的 CPU 是 i3 530 处理器,属于伪四核。想来如果四核并行求和的话,耗时应是非并行的 25% 左右。
于是编码进行验证:
并行求和效率测试先生成一个满是随机数的数组:
var random = new Random();var data = Enumerable.Range(1, 67108864) .Select(i => (long)random.Next(int.MaxValue)) .ToArray();
这个数组比较大,生成大约要 5 秒钟时间。
测试时使用 Stopwatch 类计时:
Stopwatch w = new Stopwatch();//并行w.Start();var sum1 = data.AsParallel().Sum();w.Stop();Console.WriteLine(w.ElapsedMilliseconds);//非并行w.Reset();w.Start();var sum2 = data.Sum();w.Stop();Console.WriteLine(w.ElapsedMilliseconds);
执行多次结果如下(考虑到执行顺序或许会对结果产生影响,后面五次的执行是把非并行求和放在并行求和前面进行测试的):
1 2 3 4 5 6 7 8 9 10
并行 385 385 376 409 437 342 419 347 379 342
非并行 733 666 668 665 669 673 667 670 669 672
(单位:毫秒)
平均起来并行求和只用了 57% 的时间,相对于非并行。虽然效率有提高,但离我们最初设想的 25% 差多了。
莫非只用了两个核,不如从任务管理器中查看下 CPU 使用情况。但是单次执行时间不到一秒,时间太短,不易看清,在代码中再加上个循环:
for (int i = 0; i < 100; i++){ var sum = data.AsParallel().Sum();}
执行,则从下图中可以看出,四核已全部用上:
看来应该是并行求和算法的问题了。不如自己改进下。
简单并行求和算法思路很简单,把数组分成多块,每个 CPU 核心负责一块的求和,最后把每块的和加起来就是了:
results = r = Parallel.For(0, partitionCount, i => results[i] = PartitionSum(data, partitionLength * i, partitionLength));var sum3 = results.Sum(); //将各块的和加起来
PartitionSum 是一个函数,用来计算某一块的和:
PartitionSum(long[] source, int start, int length){ long result = 0; int end = start + length; if (end > source.Length) end = length; for (int i = start; i < end; i++) result += source[i]; return result;}
测试时间为:
1 2 3 4 5 6 7 8 9 10
并行改进 153 161 151 154 152 156 157 153 155 153
(单位:毫秒)
占非并行执行时间的 23%,与我们最初的预计差不多,要好些。
总结本文的测试虽然比较简单,粗陋,也不权威,但也从一定程度上说明了 .NET 4 的并行求和算法效率不高。
本文仅对 .NET 4 的并行求和进行了测试,只涉及到 .NET 4 中并行的很少一部分。.NET 4 的并行求和算法效率不高并不表示其它部分效率也不高。
后记:真正四核 CPU Xeon E5606 上的测试结果回复中有朋友提到可能是伪四核 CPU 的原因,于是我找了一台服务器,装有 Xeon E5606,是货真价实的四核处理器。
测试结果如下:
1 2 3 4 5 6 7 8 9 10
非并行 858 863 861 859 858 862 863 859 861 859
并行 250 248 280 248 248 248 249 248 249 249
并行改进 121 121 121 121 121 121 121 121 121 121
(单位:毫秒)
相对非并行,.NET 并行用时约 29%,我的简单改进算法约 14%。
29% 与最初预期的 25% 很接近,看来原因就在伪四核上了。