在Linux内核中,____cacheline_aligned_in_smp用于结构体的对齐。

8
在Linux内核中,为什么许多结构使用____cacheline_aligned_in_smp宏?它有助于在访问结构时提高性能吗?如果是,那么如何实现的?

它可以降低SMP系统中缓存嗅探逻辑所需同步的缓存行数量。 - Paul R
嗨,保罗, 我找到了一个解释:这是一个宏,会展开为 __attribute__(gcc 扩展),该扩展要求所声明的变量分配在 L1 缓存行大小的整数倍地址上。 http://vivekbhadra.wordpress.com/article/linux-cacheline-aligned-in-smp-macro-3c84lj4klzp0d-15/这正确吗? - K_K
抱歉,是的,我认为那是一个常识 - 总体思路是通过将分配对齐到缓存行以最小化缓存占用,并因此在 SMP 系统中减少缓存嗅探流量。 - Paul R
3个回答

13

____cacheline_aligned指示编译器在特定架构下,将结构体或变量实例化到对应L1缓存行的开头地址,即使其L1缓存行对齐。____cacheline_aligned_in_smp类似,但只有在内核以SMP配置(即使用选项CONFIG_SMP)编译时才会实际进行L1缓存行对齐。这些定义在文件include/linux/cache.h中。

这些定义对于不通过某个分配器动态分配的变量(和数据结构)而是全局编译器分配的变量非常有用(使用动态内存分配器可以实现特定对齐内存的分配)。

将变量对齐到缓存线的原因是在SMP系统中通过硬件缓存一致性机制管理这些变量的缓存到缓存传输,以便它们的移动不会隐含地发生在其他变量移动时。这适用于性能关键代码,其中一个人预计多个CPU(核心)访问变量时会产生争用。在这种情况下,通常要避免的问题是虚假共享。

变量从缓存线的起始位置开始的内存是实现此目的的一半;还需要“仅”将应一起移动的变量“打包”在一起。一个例子是变量数组,其中数组的每个元素只由一个CPU(核心)访问:

struct my_data {
   long int a;
   int b;
} ____cacheline_aligned_in_smp cpu_data[ NR_CPUS ];

这种定义将要求编译器(在内核的SMP配置中)使每个CPU的结构体从缓存行边界开始。编译器将会隐式地在每个CPU的结构体后面分配额外的空间,以便下一个CPU的结构体也能从缓存行边界开始。

另一种方法是用缓存行大小的虚拟、未使用的字节填充数据结构:

struct my_data {
   long int a;
   int b;
   char dummy[L1_CACHE_BYTES];
} cpu_data[ NR_CPUS ];

在这种情况下,只有虚拟的、未使用的数据会被无意地移动,而每个 CPU 实际访问的数据仅会因为缓存容量不足而在缓存和内存之间移动。


很好的解释 :) - K_K

6

在任何缓存(dcache或icache)中,每个缓存行都是64字节(在x86架构中)。需要进行缓存对齐以避免缓存行的虚假共享。如果缓存行在全局变量之间共享(在内核中更常见),如果其中一个处理器在其缓存中更改了一个全局变量,则将该缓存行标记为脏。在剩余的CPU缓存行中,它变成了陈旧的条目,需要从内存中刷新并重新获取。这可能导致缓存行未命中,需要更多的CPU周期。这会降低系统的性能。请记住,这适用于全局变量。大多数内核数据结构使用此方法以避免缓存行未命中。


4
Linux非常类似于TLB,对CPU高速缓存进行管理。CPU缓存(例如TLB缓存)利用程序往往表现出局部性这一事实。为避免每次引用都需要从主存中获取数据,CPU将在高速缓存中缓存非常少量的数据。通常有两个级别称为1级和2级CPU缓存。2级CPU缓存比L1缓存更大但速度较慢,但是Linux仅关注1级或L1缓存。
CPU缓存由线组织。每行通常相当小,通常为32字节,并且每行对齐到其边界大小。换句话说,32字节的高速缓存行将对齐到32字节地址。在Linux中,行的大小为L1_CACHE_BYTES,它由每个体系结构定义。
地址如何映射到高速缓存行因体系结构而异,但映射可以分为三类:直接映射、关联映射和组关联映射。直接映射是最简单的方法,其中每个内存块仅映射到一个可能的高速缓存行。使用关联映射,任何内存块都可以映射到任何高速缓存行。组关联映射是一种混合方法,其中任何内存块都可以映射到任何行,但仅限于可用行的子集。
无论映射方案如何,它们都具有一件共同的事情,即靠得很近且对齐高速缓存大小的地址很可能使用不同的行。因此,Linux采用简单的技巧来尝试最大化缓存使用率:
- 访问频繁的结构字段位于结构的开始处,以增加只需要一个线来寻址常见字段的机会; - 结构中的不相关项应尽量相隔至少缓存大小字节,以避免在多个CPU之间出现虚假共享; - 一般高速缓存中的对象(例如mm_struct高速缓存)与L1 CPU高速缓存对齐,以避免虚假共享。
如果CPU引用的地址不在高速缓存中,则会发生高速缓存未命中,并从主内存中获取数据。高速缓存未命中的代价非常高,因为对高速缓存的引用通常可以在少于10ns的时间内执行,而对主内存的引用通常将花费100ns至200ns之间的时间。基本目标是尽可能地保持高速缓存命中率高,高速缓存未命中率低。
就像某些体系结构不自动管理其TLB一样,某些体系结构也不自动管理其CPU高速缓存。这些钩子放置在虚拟到物理映射更改的位置(例如在页面表更新期间)。CPU高速缓存刷新应始终首先进行,因为某些CPU要求虚拟到物理映射存在于从高速缓存中刷新虚拟地址时。更多信息请参见此处

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