Ren*_* R. 12 c++ parallel-processing multithreading caching
我编写了以下简短的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;
}
Run Code Online (Sandbox Code Playgroud)
我认为这个想法很容易理解.如果你设置
#define PADDING 16
Run Code Online (Sandbox Code Playgroud)
每个线程将在单独的缓存行上工作(假设缓存行的长度为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;
}
Run Code Online (Sandbox Code Playgroud)
尽管在这种情况下这个解决方案对我来说很好,但我不确定它是否是一般的好方法.所以这是我的问题:
有没有什么常见的方法让内存中的对象与缓存行对齐,而不是我在上面的例子中所做的那样?
(使用g ++ MinGW Win32 x86 v.4.8.1 posix dwarf rev3)
Dav*_*eas 12
您应该能够从编译器请求所需的对齐:
alignas(64) int arr[PARALELL * PADDING]; // align the array to a 64 byte line
Run Code Online (Sandbox Code Playgroud)