我正在尝试使用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功率分配给并行计算?
我注意到在SO before上提出了类似的问题,结果更令人失望。但是,该问题还涉及第三方库中较差的并行化。我主要关注的是核心库中基本操作的并行化。
更新
我在一些评论中向我指出,我使用的CPU只有4个物理内核,如果启用超线程,系统可以看到8个内核。为此,我禁用了超线程并重新进行了基准测试。
通过超线程禁用,我的计算现在更快,无论是并行还是(我认为是)顺序for
循环。 for
循环期间的CPU利用率高达约。 Parallel.For
循环期间45%(!!!)和100%。
for
循环15.6秒的计算时间(超线程启用的速度快两倍)和Parallel.For
的6.2秒(比超线程启用)。与Parallel.For
的效果比现在只有 2.5 ,在4个真核上运行。
因此,尽管禁用了超线程,但性能比仍远低于预期。另一方面,有趣的是for
循环期间CPU利用率如此之高?在这个循环中是否会出现某种内部并行化?
答案 0 :(得分:23)
即使您没有使用锁,使用全局变量也会引入严重的同步问题。为变量赋值时,每个核心必须访问系统内存中的相同位置,或者在访问之前等待另一个核心完成。 通过使用较轻的Interlocked.Add方法在操作系统级别以原子方式向值添加值,可以避免没有锁定的损坏,但是由于争用仍然会导致延迟。
执行此操作的正确方法是更新线程局部变量以创建部分和,并将所有部分加到最后的单个全局和中。 Parallel.For有一个重载就是这样。 MSDN甚至在How To: Write a Parallel.For Loop that has Thread Local Variables
使用了sumation int[] nums = Enumerable.Range(0, 1000000).ToArray();
long total = 0;
// Use type parameter to make subtotal a long, not an int
Parallel.For<long>(0, nums.Length, () => 0, (j, loop, subtotal) =>
{
subtotal += nums[j];
return subtotal;
},
(x) => Interlocked.Add(ref total, x)
);
每个主题更新自己的小计值,并在完成后使用Interlocked.Add更新全局总计。
答案 1 :(得分:6)
Parallel.For和Parallel.ForEach将使用它认为合适的一定程度的并行性,平衡设置和拆除线程的成本以及它期望每个线程将执行的工作。 .NET 4.5 made several improvements to performance (including more intelligent decisions on the number of threads to spin up) compared to previous .NET versions.
请注意,即使它是为每个核心启动一个线程,上下文切换,false sharing问题,资源锁定和其他问题可能会阻止您实现线性可伸缩性(通常,不一定与您的特定代码示例)。
答案 2 :(得分: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);
}
);
分区程序将为每个任务创建最佳作业部分,使用线程的服务任务将有更少的时间。如果可以的话,请对此解决方案进行基准测试,并告诉我们它是否能更好地加速。
答案 3 :(得分:1)
每个例子的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)