并行计算内存访问瓶颈

时间:2011-11-27 05:39:59

标签: c++ multithreading memory optimization

以下算法在我的程序中迭代运行。没有下面指出的两条线,运行它只需要1.5倍。这对我来说非常令人惊讶。然而,更糟糕的是,运行带有这两行的可以将完成时的完成率提高到4.4倍(6.6X不运行整个算法)。另外,它导致我的程序无法扩展到超过~8个核心。事实上,当在单核上运行时,这两条线只会将时间增加到1.7倍,考虑到它们的作用,这仍然太高。我已经排除它与我程序中其他地方的修改数据的影响有关。

所以我想知道是什么原因引起的。可能与缓存有关吗?

void NetClass::Age_Increment(vector <synapse> & synapses, int k)  
{
    int size = synapses.size();
    int target = -1;

    if(k > -1)
    {
        for(int q=0, x=0 ; q < size; q++)
        {
            if(synapses[q].active)
                synapses[q].age++;
            else
            {
                if(x==k)target=q;
                x++;
            }
        }
        /////////////////////////////////////Causing Bottleneck/////////////
        synapses[target].active = true;
        synapses[target].weight = .04 + (float (rand_r(seedp) % 17) / 100);
        ////////////////////////////////////////////////////////////////////
    }

    else
    {
        for(int q=0 ; q < size; q++)
            if(synapses[q].active)
                synapses[q].age++;
    }
}

更新:将两个问题行更改为:

bool x = true;
float y = .04 + (float (rand_r(seedp) % 17) / 100);

删除问题。建议可能与内存访问有关吗?

3 个答案:

答案 0 :(得分:6)

每个线程修改所有其他读取的内存:

for(int q=0, x=0 ; q < size; q++)
   if(synapses[q].active) ... // ALL threads read EVERY synapse.active
...
synapses[target].active = true; // EVERY thread writes at leas one synapse.active

这些来自不同线程的同一地址的读写会导致大量的缓存失效,这将导致您描述的症状。解决方案是避免在循环内部写入,并且将写入移动到局部变量的事实再次证明问题是缓存失效。请注意,即使您不编写正在阅读的理智字段(active),您也可能会因错误共享而看到相同的症状,因为我怀疑activeageweight共享一个缓存行。

有关详细信息,请参阅CPU Caches and Why You Care

最后需要注意的是,对activeweight的分配,更不用说age++增量似乎都非常不安全。此类更新的互锁操作或锁定/互斥保护是强制性的。

答案 1 :(得分:2)

如果size相对较小,那么对PRNG的调用,整数除法以及浮点除法和加法都会让程序执行得多。你正在做相当多的工作,所以它会增加运行时似乎是合乎逻辑的。此外,因为您告诉编译器将数学运算为float而不是double,这可能会在某些系统上进一步增加时间(本机浮点数为双倍)。您是否考虑过int s的固定点表示?

我不能说为什么它会因为更多内核而变得更糟,除非你超过操作系统给出的程序核心数(或者如果系统的rand_r是使用锁定或特定于线程实现的数据以维持其他状态)。

另请注意,在将target用作数组索引之前,永远不会检查for是否有效,如果它已经从{{1}}循环仍然设置为-1,那么所有投注都将关闭程序

答案 2 :(得分:2)

尝试重新引入这两行,但没有rand_r,只是为了看看你是否会遇到相同的性能下降。如果不这样做,这可能表示rand_r是内部序列化的(例如通过互斥锁),因此您需要找到一种方法来同时生成随机数。

另一个值得关注的领域是false sharing(如果你有时间,请看看Herb Sutter的videoslides对待这个问题,等等。实质上,如果您的线程碰巧修改了足够接近同一高速缓存行的不同内存位置,则高速缓存一致性硬件可以有效地序列化内存访问并破坏可伸缩性。这使得难以诊断的原因是这些内存位置可能逻辑独立,并且在运行时它们可能不是直观地显而易见。如果您怀疑虚假共享,请尝试添加一些填充以将这些内存位置分开。