20

我正在尝试通过使用来加快计算时间Parallel.Forfor我有一个 8 核的 Intel Core i7 Q840 CPU,但与顺序循环相比,我只能获得 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 上提出过类似的问题,结果更令人失望。但是,该问题还涉及第三方库中较差的并行化。我主要关心的是核心库中基本操作的并行化。

更新

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

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

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

因此,尽管禁用了超线程,但性能比仍然大大低于预期。另一方面,for循环期间 CPU 利用率如此之高是不是很有趣?在这个循环中是否还会发生某种内部并行化?

4

4 回答 4

27

使用全局变量可能会引入严重的同步问题,即使您不使用锁也是如此。当您为变量赋值时,每个内核都必须访问系统内存中的同一位置,或者等待另一个内核完成后再访问它。您可以通过使用较轻的Interlocked.Add方法在操作系统级别以原子方式将值添加到总和来避免没有锁的损坏,但您仍然会由于争用而延迟。

正确的方法是更新线程局部变量以创建部分和,并将它们全部添加到最后的单个全局和中。Parallel.For有一个重载可以做到这一点。MSDN 甚至在How To: Write a Parallel.For Loop that has Thread Local Variables 中有一个使用求和的示例

        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更新全局总计。

于 2012-06-01T08:22:10.340 回答
6

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

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

于 2012-06-01T08:01:36.553 回答
6

我认为计算增益是如此之低,因为您的代码“太容易”而无法在每次迭代中处理其他任务 - 因为 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);
    }
);

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

于 2014-04-12T20:11:04.173 回答
0

foreach vs parallel for each 一个例子

    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();

在此处输入图像描述

我的处理器

Intel® Core™ i7-620M 处理器(4M 高速缓存,2.66 GHz)

于 2016-07-18T10:43:18.543 回答