使用Parallel.For令人失望的性能

我试图通过使用Parallel.For来加快我的计算时间。 我有一个带有8个内核的英特尔酷睿i7 Q840 CPU,但与顺序for循环相比,我只能获得4的性能比。 这是否与Parallel.For一样好,或者可以调整方法调用以提高性能?

这是我的测试代码,顺序:

 var loops = 200; var perloop = 10000000; var sum = 0.0; for (var k = 0; k < loops; ++k) { var sumk = 0.0; for (var i = 0; i < perloop; ++i) sumk += (1.0 / i) * i; sum += sumk; } 

并行:

 sum = 0.0; Parallel.For(0, loops, k => { var sumk = 0.0; for (var i = 0; i < perloop; ++i) sumk += (1.0 / i) * i; sum += sumk; }); 

我并行化的循环涉及使用“全局”定义的变量sum ,但这应该只是并行化循环中总时间的一小部分。

在发布版本(“优化代码”标志集)中,顺序for循环在我的计算机上需要33.7秒,而Parallel.For循环需要8.4秒,性能比仅为4.0。

在任务管理器中,我可以看到在顺序计算期间CPU利用率为10-11%,而在并行计算期间仅为70%。 我试图明确设置

 ParallelOptions.MaxDegreesOfParallelism = Environment.ProcessorCount 

但无济于事。 我不清楚为什么不将所有CPU功率分配给并行计算?

顺序与并行CPU利用率

我注意到之前在SO上提出了类似的问题,结果更加令人失望。 但是,该问题还涉及第三方库中较差的并行化。 我主要关心的是核心库中基本操作的并行化。

UPDATE

我在一些评论中向我指出,我使用的CPU只有4个物理内核,如果启用超线程,系统可以看到8个内核。 为此,我禁用了超线程并重新进行了基准测试。

禁用超线程后,我的计算现在更快 ,无论是并行还是(我认为是)顺序循环。 for循环期间的CPU利用率高达约。 Parallel.For循环期间45%(!!!)和100%。

for循环的计算时间为15.6 s(比启用超线程的速度快两倍)和Parallel.For 6.2 s(比启用超线程时好25%)。 与Parallel.For性能比现在只有2.5 ,在4个真实核心上运行。

因此,尽管禁用了超线程,性能比仍然远低于预期。 另一方面,令人感兴趣的是在for循环期间CPU利用率如此之高? 在这个循环中是否会出现某种内部并行化?

即使您没有使用锁,使用全局变量也会引入严重的同步问题。 为变量赋值时,每个核心都必须访问系统内存中的相同位置,或者在访问之前等待另一个核心完成。 通过使用较轻的Interlocked.Add方法在操作系统级别以primefaces方式向值添加值,可以避免没有锁定的损坏,但是由于争用仍然会导致延迟。

执行此操作的正确方法是更新线程局部变量以创建部分总和,并将所有部分加到最后的单个全局和中。 Parallel.For有一个重载就可以做到这一点。 MSDN甚至在如何:写一个具有线程局部变量的Parallel.For循环中使用了sumation

  int[] nums = Enumerable.Range(0, 1000000).ToArray(); long total = 0; // Use type parameter to make subtotal a long, not an int Parallel.For(0, nums.Length, () => 0, (j, loop, subtotal) => { subtotal += nums[j]; return subtotal; }, (x) => Interlocked.Add(ref total, x) ); 

每个线程更新其自己的小计值,并在完成时使用Interlocked.Add更新全局总计

Parallel.For和Parallel.ForEach将使用它认为合适的一定程度的并行性,平衡设置和拆除线程的成本以及它期望每个线程将执行的工作。 与以前的.NET版本相比,.NET 4.5对性能进行了一些改进(包括对要启动的线程数量做出更明智的决策)。

请注意,即使它是每个核心启动一个线程,上下文切换, 错误共享问题,资源锁定和其他问题可能会阻止您实现线性可伸缩性(通常,不一定与您的特定代码示例)。

我认为计算收益是如此之低,因为你的代码“太容易”在每次迭代中处理其他任务 – 因为parallel.for只是在每次迭代中创建新任务,所以这需要时间来在线程中为它们提供服务。 我会这样:

 int[] nums = Enumerable.Range(0, 1000000).ToArray(); long total = 0; Parallel.ForEach( Partitioner.Create(0, nums.Length), () => 0, (part, loopState, partSum) => { for (int i = part.Item1; i < part.Item2; i++) { partSum += nums[i]; } return partSum; }, (partSum) => { Interlocked.Add(ref total, partSum); } ); 

分区程序将为每个任务创建最佳工作部分,使用线程的服务任务将有更少的时间。 如果可以的话,请对此解决方案进行基准测试,并告诉我们它是否能更快地加速。

每个例子的foreach vs parallel

  for (int i = 0; i < 10; i++) { int[] array = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29 }; Stopwatch watch = new Stopwatch(); watch.Start(); //Parallel foreach Parallel.ForEach(array, line => { for (int x = 0; x < 1000000; x++) { } }); watch.Stop(); Console.WriteLine("Parallel.ForEach {0}", watch.Elapsed.Milliseconds); watch = new Stopwatch(); //foreach watch.Start(); foreach (int item in array) { for (int z = 0; z < 10000000; z++) { } } watch.Stop(); Console.WriteLine("ForEach {0}", watch.Elapsed.Milliseconds); Console.WriteLine("####"); } Console.ReadKey(); 

在此处输入图像描述

我的CPU

英特尔®酷睿™i7-620M处理器(4M高速缓存,2.66 GHz)