对齐缓存行和了解缓存行大小

71
为了防止伪共享(false sharing),我想将数组的每个元素都对齐到一个缓存行(cache line)。所以首先需要知道缓存行的大小,然后为每个元素分配相应数量的字节。其次,我希望数组的起始地址也能对齐到一个缓存行。
我使用Linux和8核x86平台。第一步是如何找到缓存行的大小。第二步是如何在C语言中实现对齐到缓存行。我使用gcc编译器。
例如,假设缓存行大小为64,则结构应如下所示:
element[0] occupies bytes 0-63
element[1] occupies bytes 64-127
element[2] occupies bytes 128-191
假设0-63对齐到缓存行,等等其他情况都是如此。

2
也许这个可以有所帮助:https://dev59.com/M3RA5IYBdhLWcg3w4SDo - Tony The Lion
1
但它没有展示如何使用gcc对齐缓存。 - MetallicPriest
可能是Programmatically get the cache line size?的重复问题。 - Ciro Santilli OurBigBook.com
2
使用编译时常量64字节作为缓存行大小并不是一个坏主意,这样编译器就可以将其嵌入到关心它的函数中。让编译器为运行时变量缓存行大小生成代码可能会消耗一些对齐的好处,特别是在自动向量化的情况下,如果编译器知道指针对齐到缓存行宽度(比SIMD向量宽度更宽),则有助于编译器生成更好的代码。 - Peter Cordes
7个回答

94

我正在使用 Linux 和 8 核 x86 平台,想知道如何查找缓存行大小。

$ getconf LEVEL1_DCACHE_LINESIZE
64

将该值作为宏定义传递给编译器。

$ gcc -DLEVEL1_DCACHE_LINESIZE=`getconf LEVEL1_DCACHE_LINESIZE` ...

在运行时,sysconf(_SC_LEVEL1_DCACHE_LINESIZE) 可以用来获取 L1 缓存大小。


1
这些 sysconf() 是在哪里规定的?POSIX / IEEE Std 1003.1-20xx 吗? - Brian Cain
1
@BrianCain http://pubs.opengroup.org/onlinepubs/9699919799/functions/sysconf.html - Maxim Egorushkin
@BrianCain 我使用Linux,所以我只需执行man sysconf。Linux并不完全符合POSIX标准,因此Linux特定的文档通常更有用。有时它可能已经过时,所以你只需执行egrep -nH -r /usr/include -e '\b_SC' - Maxim Egorushkin
1
在Mac的情况下,请使用sysctl hw.cachelinesize - Dení
通常情况下,编译时常量行大小要好得多,我宁愿硬编码64而不是调用sysconf。编译器甚至不知道它是2的幂次方,因此您必须手动执行诸如offset = ptr & (linesize-1)的操作以获取余数或位扫描+右移来实现除法。在性能敏感的代码中不能使用/ - Peter Cordes
显示剩余3条评论

42
要知道缓存的大小,您需要查阅处理器文档,据我所知,没有编程方式可以实现。但是好处是,大多数缓存行都是基于英特尔标准的标准大小。在x86上,缓存行为64个字节,但是为了防止虚假共享,您需要遵循针对处理器的指南(英特尔的基于netburst的处理器有一些特殊说明),通常需要将其对齐到64字节(英特尔还声明您应该避免跨越16字节边界)。
在C或C++中执行此操作需要使用标准的aligned_alloc函数或其中一个编译器特定的说明符,例如__attribute__((align(64)))__declspec(align(64))。要在结构体成员之间填充以将它们拆分到不同的缓存行中,您需要插入足够大的成员以将其对齐到下一个64字节边界。

2
@MetallicPriest:gcc g++ 都支持 __attributes__ - Sebastian Mach
1
@MetallicPriest:mmapVirtualAlloc分配页面对齐的内存,通常页面粒度为64kb(在Windows下),由于64kb是2的幂,因此它将被正确对齐。 - Necrolis
1
你可以通过编程方式获取缓存行大小。请查看这里。此外,你不能将x86的缓存行一般化为64字节。这只适用于最近的处理器。 - tothphu
4
C++11 新增了 alignas,这是一种指定对齐方式的可移植方式。 - NoSenseEtAl
1
@NoSenseEtAl alignas 官方只支持类型大小最多为 std::max_align_t 的对齐,这通常是 long double 的对齐要求,即 8 或 16 字节 - 不幸的是不支持 64 字节。例如,请参见 https://dev59.com/dFUM5IYBdhLWcg3wEM_G - Carlo Wood
显示剩余7条评论

14

另一个简单的方法就是直接cat /proc/cpuinfo:

grep cache_alignment /proc/cpuinfo

1
也许你想要移除一个无用的cat使用。 - maxschlepzig

9

没有完全便携的方法来获取缓存行大小。但如果您使用的是x86/64架构,可以调用cpuid指令以获取关于高速缓存的所有所需信息,包括大小、缓存行大小、级数等。

http://softpixel.com/~cwright/programming/simd/cpuid.php

(向下滚动一点,该页面介绍了SIMD,但也有一个获取缓存行的部分。)

至于对齐数据结构,同样没有完全便携的方法。GCC和VS10有不同的方法来指定结构体的对齐方式。 "hack"的一种方法是添加未使用的变量作为填充,直到它与所需的对齐方式匹配。

要对齐您的malloc(),所有主流编译器也都有用于此目的的对齐内存分配函数。


8

3
我知道这是你自己的问题,但是为了未来的读者,你可以回答两个部分 :-) - Steve Jessop
Steve,你知道通过mmap映射的内存是否对齐到缓存行吗? - MetallicPriest
3
我不认为这是Posix所保证的,但如果Linux总是选择页对齐的地址,而不仅仅是缓存行对齐的话,我也不会感到惊讶。Posix规定如果调用方指定第一个参数(地址提示),那么必须是页对齐的,并且映射本身总是整数倍的页面。虽然这强烈暗示了一些东西,但实际上并没有保证任何事情。 - Steve Jessop
1
是的,mmap 只能按页面工作,而页面总是大于缓存行。即使在某些理论上奇怪的架构中,也有极好的理由为什么缓存行不会比页面更大(缓存通常是物理标记的,因此一个线不能跨越 2 个虚拟页面而不给 CPU 设计者带来极大的痛苦)。 - Peter Cordes

3

这是我制作的一张表格,其中列出了大多数Arm/Intel处理器。在定义常量时可以使用它作为参考,这样你就不必为所有架构概括缓存行大小。

对于C++,希望我们很快可以看到硬件接口大小,这应该是一个准确获取此信息的方法(假设您告诉编译器您的目标架构)。


1
编译器不愿实现hardware_destructive_interference_size,因为你真的希望它是一个编译时常量,但如果你为可以在多个相同ISA的CPU上运行的“通用”目标进行编译,则它并不总是如此。保守的选择是可能的,但不能保证未来兼容性。(例如128字节,以适应具有64字节行和喜欢完成对齐行对的L2空间预取的当前x86 CPU。(主流Intel)) - Peter Cordes

2
如果有人想知道如何在C++中轻松实现这一点,我已经建立了一个库,其中包含一个CacheAligned<T>类,它处理确定缓存行大小以及对您的T对象进行对齐,通过在CacheAligned<T>对象上调用.Ref()来引用。如果您事先知道缓存行大小,或者只想坚持使用非常普遍的64(字节)值,也可以使用Aligned<typename T, size_t Alignment>
您可以访问https://github.com/NickStrupat/Aligned

@James - alignas是C++11的特性,不适用于C++03。而且在一些苹果平台上无法使用。在他们的某些操作系统中,苹果提供了一个古老的C++标准库,它假装自己是C++11,但缺少unique_ptralignas等功能。 - jww
1
@James,此外,标准只要求alignas支持最多16个字节,因此任何更高的值都不具备可移植性。而且,由于几乎所有现代处理器的缓存行大小为64个字节,除非您知道编译器支持alignas(64),否则alignas没有用处。 - Nick Strupat
1
alignas 也出现在 C11 中,不仅仅是 C++11。 - Alnitak
alignas 官方只支持类型大小最多为 std::max_align_t 的对齐,这通常是 long double 的对齐要求,即 8 或 16 字节 - 不幸的是不是 64。 - Carlo Wood
1
@NickStrupat 看起来 C++17 终于支持按缓存行大小进行对齐了。我的最后一条评论似乎也不再正确,至少对于 C++17 来说是这样(问题只是 operator new 不保证返回比 std::max_align_t 更好的内存对齐)。我刚刚发现了这个链接: https://en.cppreference.com/w/cpp/thread/hardware_destructive_interference_size - Carlo Wood
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接