用的Parallel.For表现令人失望(Disappointing performance wit

2019-07-29 16:29发布

我试图通过加快我的计算时间Parallel.For 。 我有8个内核的英特尔酷睿i7 Q840 CPU,但我只设法得到比起依次为4的性能比for循环。 这是好,因为它可以得到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 S,而Parallel.For循环花费8.4秒,只有4.0性能比。

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

ParallelOptions.MaxDegreesOfParallelism = Environment.ProcessorCount

但无济于事。 为什么不是所有的CPU功率被分配到并行计算目前尚不清楚对我?

我注意到,类似的问题已经提出了对SO 前 ,有一个更令人失望的结果。 然而,这个问题还参与了第三方库逊色并行。 我最关注的是在核心库的基本操作的并行化。

UPDATE

有人指出,给我一些,我现在用的CPU只有4个物理核心的意见,如果启用超线程这是该系统作为8个内核可见的。 对于它的缘故,我禁用超线程并重新基准。

随着禁用超线程,我的计算,现在要更快一些,无论是平行的,也是(我认为是)的顺序for循环。 在CPU利用率for循环达约 45%(!!!)和期间100% Parallel.For循环。

计算时间for循环15.6秒和6.2 S代表(快两倍与超线程启用更多) Parallel.For (当启用了超线程优于25%)。 与性能比Parallel.For现在只有2.5,在4个真正的内核上运行。

所以性能比基本上仍低于预期,尽管超线程被禁用。 在另一方面,这是耐人寻味的是CPU使用率是在如此之高for循环? 难道还有某种内在并行的在这个循环怎么回事呢?

Answer 1:

使用全局变量可以引入显著同步的问题,即使你不使用锁。 当你分配一个值的变量每个核心将获得在系统内存中访问到同一个地方,或者等待其他核心来访问它之前完成。 你能避免腐败不使用轻锁Interlocked.Add方法添加值的总和原子,在操作系统级别,但你仍然会得到因争延迟。

这样做的正确方法是更新线程局部变量创建部分和与所有的人都在末尾添加到一个单一的全球总和。 的Parallel.For都有,不只是这个过载。 MSDN即使使用Sumation公司在有一个例子写的Parallel.For循环具有线程局部变量:如何

        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当它完成更新全球总量



Answer 2:

的Parallel.For和Parallel.ForEach将使用的程度,那感觉是合适的并行性,平衡成本,以建立和拆除线程和工作,预计每个线程将执行。 .NET 4.5做出业绩几项改进(包括螺纹旋转起来的次数更为明智的决定)相比以前版本的.NET。

需要注意的是,即使它是旋转起来每个内核,上下文切换,一个线程假共享问题,资源锁等问题可能会阻止你实现线性可扩展性(一般,不一定与您的特定代码示例)。



Answer 3:

我认为,在计算收益是如此之低,因为你的代码是“太容易”的其他工作任务的每一次迭代 - 因为刚才的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);
    }
);

分区将创造就业的最佳是对每一个任务,都会有一个与线程服务任务的时间更少。 如果可以,请标杆这个解决方案,并告诉我们,如果它获得更好的加速。



Answer 4:

的foreach VS为每个示例平行

    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的)



文章来源: Disappointing performance with Parallel.For