高速缓存行,错误的共享和对齐

问题描述 投票:14回答:3

我编写了以下简短的C ++程序来重现Herb Sutter中所述的虚假共享效果:

说,我们希望执行总计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>#cores。另一方面,如果将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)

c++ multithreading caching parallel-processing
3个回答
13
投票

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

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

4
投票

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


0
投票

在现代C ++(17及更高版本)中,您应该使用hardware_constructive_interference_size

© www.soinside.com 2019 - 2024. All rights reserved.