并行计算内存访问瓶颈

Parallel computing memory access bottleneck

本文关键字:访问 内存 并行计算      更新时间:2023-10-16

以下算法在我的程序中迭代运行。运行它,如果没有下面指出的两条线,所需的时间是没有的1.5倍。这对我来说是非常令人惊讶的。然而,更糟糕的是,用运行这两行将完成度提高到没有它们运行的4.4X(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);

删除问题。暗示这可能与内存访问有关?

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

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

这种从不同线程对同一地址的读取和写入会导致大量缓存失效,这将导致您所描述的症状。解决方案是避免在循环内写入,而将写入移动到局部变量的事实再次证明了问题是缓存无效。请注意,即使您不编写正在读取的sane字段(active(,您也可能会由于错误共享而看到相同的症状,因为我怀疑activeageweight共享一条缓存线。

有关更多详细信息,请参阅CPU缓存和为什么关心

最后要注意的是,对activeweight的赋值,更不用说age++的增量了,看起来都非常不安全。这种更新的互锁操作或锁/互斥保护是强制性的。

试着重新引入这两行,但不使用rand_r,只是为了看看是否会出现同样的性能恶化。如果你不这样做,这可能是rand_r在内部序列化的迹象(例如通过互斥(,所以你需要找到一种更并发地生成随机数的方法。

另一个潜在的关注领域是虚假分享(如果你有时间,可以看看赫伯·萨特关于这个主题的视频和幻灯片等(。从本质上讲,如果您的线程碰巧修改了不同的内存位置,这些位置足够近,以至于落入同一个缓存行,那么缓存一致性硬件可能会有效地串行化内存访问并破坏可扩展性。使这一点难以诊断的是,这些内存位置在逻辑上可能是独立的,而且在运行时它们可能不会靠得很近。如果您怀疑存在错误共享,请尝试添加一些填充以将这些内存位置分隔开。

如果size相对较小,那么对PRNG、整数除法、浮点除法和加法的调用会大大增加程序执行量,这一点也不奇怪。您正在做相当多的工作,因此增加运行时间似乎是合乎逻辑的。此外,由于您告诉编译器按照float而不是double进行计算,这可能会在某些系统上进一步增加时间(其中本机浮点是双倍的(。你考虑过ints的不动点表示吗?

我不能说为什么它会随着内核的增加而扩展得更糟,除非你超过了操作系统为你的程序提供的内核数量(或者如果你的系统的rand_r是使用锁定或线程特定的数据来实现的,以保持额外的状态(。

还要注意,在将target用作数组索引之前,您永远不会检查它是否有效,如果它退出了仍设置为-1的for循环,则程序的所有赌注都将取消。