问题 缓存行,错误共享和对齐


我编写了以下简短的C ++程序来重现虚假的共享效果,如下所述 草药萨特

比如说,我们想要执行总量的WORKLOAD整数运算,我们希望它们平均分配给一些(PARALLEL)线程。出于此测试的目的,每个线程将从整数数组中递增其自己的专用变量,因此该过程可以理想地并行化。

void thread_func(int* ptr)
{
    for (unsigned i = 0; i < WORKLOAD / PARALLEL; ++i)
    {
        (*ptr)++;
    }
}

int main()
{
    int arr[PARALLEL * PADDING];
    thread threads[PARALLEL];

    for (unsigned i = 0; i < PARALLEL; ++i)
    {
        threads[i] = thread(thread_func, &(arr[i * PADDING]));
    }
    for (auto& th : threads)
    {
        th.join();
    }
    return 0;
}

我认为这个想法很容易理解。如果你设置

#define PADDING 16

每个线程将在单独的缓存行上工作(假设缓存行的长度为64字节)。因此,结果将是加速的线性增加,直到PARALLEL> #core。另一方面,如果将PADDING设置为低于16的任何值,则应该遇到严重的争用,因为现在至少有两个线程可能在同一高速缓存行上运行,但是受到内置硬件互斥锁的保护。我们希望我们的加速不仅在这种情况下是次线性的,而且即使总是<1,因为看不见的锁定车队。

现在,我的第一次尝试几乎满足了这些期望,但PADDING避免错误共享所需的最小值大约是8而不是16。我很困惑大约半小时,直到我得出明显的结论,我无法保证我的数组与主内存中的缓存行的开头完全对齐。实际对齐可能根据许多条件而变化,包括阵列的大小。

在这个例子中,当然没有必要让我们以特殊的方式对齐数组,因为我们可以将PADDING保持在16并且一切正常。但人们可以想象一下,它确实会产生影响,某个结构是否与高速缓存行对齐。因此,我添加了一些代码行来获取有关数组实际对齐的一些信息。

int main()
{
    int arr[PARALLEL * 16];
    thread threads[PARALLEL];
    int offset = 0;

    while (reinterpret_cast<int>(&arr[offset]) % 64) ++offset;
    for (unsigned i = 0; i < PARALLEL; ++i)
    {
        threads[i] = thread(thread_func, &(arr[i * 16 + offset]));
    }
    for (auto& th : threads)
    {
        th.join();
    }
    return 0;
}

尽管在这种情况下这个解决方案对我来说很好,但我不确定它是否是一般的好方法。所以这是我的问题:

有没有什么常见的方法让内存中的对象与缓存行对齐,而不是我在上面的例子中所做的那样?

(使用g ++ MinGW Win32 x86 v.4.8.1 posix dwarf rev3)


10972
2017-08-14 15:56


起源

的VirtualAlloc?它会咳出页面,所以必须对齐。 - Martin James
我很惊讶你看到任何差异。编译器应该保持 *ptr 在寄存器内=从而隐藏虚假共享惩罚。 - Mysticial
为了学习,我转向了编译器优化,所以 ptr 必须每次都取消引用。 - Rene R.
@Mysticial:我认为他正在运行一个优化版本,但不一定是这样。我对这个声明感到有些惊讶 [所需的填充]大约是8而不是16...应该与缓存行内的对齐无关。如果高速缓存行为64字节且填充为8,则在同一高速缓存行中将存在多个整数,无论​​它们是否与高速缓存行对齐。此外,对于64字节缓存行和4字节整数,您希望填充为15,而不是16?每个缓存行为1个值和15个占位符... - David Rodríguez - dribeas
@DavidRodríguez-dribeas那是对的。对齐无关紧要。但在这种情况下,它可能足以欺骗编译器做一些不那么愚蠢的事情。总是如此 - 当优化失败时,任何事情都会发生。 - Mysticial


答案:


您应该能够从编译器请求所需的对齐:

alignas(64) int arr[PARALELL * PADDING]; // align the array to a 64 byte line

11
2017-08-14 16:05



之前我没有尝试过这个,因为我认为编译器在alignas说明符上的行为可能依赖于ABI。事实证明它适用于我迄今为止测试过的每台机器。谢谢。 - Rene R.


gcc支持对齐的关键字: http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html

你可能想要这样的东西:

int arr[PARALLEL * 16] __attribute__ ((aligned (8)));

这符合 arr 到八字节的边界。

Visual Studio也有类似的功能: http://msdn.microsoft.com/en-us/library/83ythb65.aspx


4
2017-08-14 16:05