在Linux内核中,为什么许多结构使用____cacheline_aligned_in_smp
宏?访问结构时,它是否有助于提高性能?如果是,那么如何?
答案 0 :(得分:5)
____cacheline_aligned
指示编译器在对应于L1高速缓存行的开头的地址处实例化结构或变量,用于特定体系结构,即,使其与L1高速缓存行对齐。 ____cacheline_aligned_in_smp
类似,但实际上只有当内核在SMP配置中编译时(即使用选项CONFIG_SMP
),L1高速缓存行才会对齐。这些在文件include / linux / cache.h
这些定义对于未通过某些分配器动态分配的变量(和数据结构)非常有用,但它们是全局的,编译器分配的变量(类似的效果可以通过动态内存分配器完成,可以在特定的路径上分配内存)。
缓存行对齐变量的原因是在SMP系统中通过硬件缓存一致性机制管理这些变量的缓存到缓存传输,以便在移动其他变量时不会隐式发生它们的移动。这是针对性能关键代码的,其中人们期望通过多个cpus(核心)访问变量。在这种情况下,人们试图避免的常见问题是错误共享。
从高速缓存行开始处开始的变量的内存是此目的的一半;一个人还需要“包装它” 仅 应该一起移动的变量。一个例子是变量数组,其中数组的每个元素只能由一个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实际访问的数据只会从缓存移动到内存,反之亦然。
答案 1 :(得分:2)
任何缓存(dcache或icache)中的每个缓存行都是64字节(x86)架构。需要缓存对齐以避免错误共享缓存行。如果缓存行在全局变量之间共享(在内核中发生更多)如果其中一个全局变量由其缓存中的一个处理器更改,则它将该缓存行标记为脏。在剩余的CPU缓存行中,它变为过时的条目,需要刷新并从内存中重新获取。这可能会导致缓存行未命中,这需要更多的CPU周期。这降低了系统的性能。请记住,这是针对全局变量的。大多数内核数据结构使用它来避免缓存行未命中。
答案 2 :(得分:1)
Linux以与TLB非常相似的方式管理CPU Cache。像TLB缓存一样,CPU缓存利用了程序倾向于展示引用位置的事实。为避免必须从每个引用的主内存中获取数据,CPU将在CPU缓存中缓存非常少量的数据。通常,有两个级别称为1级和2级CPU缓存。 2级CPU缓存比L1缓存更大但更慢,但Linux只关注1级或L1级缓存。
CPU缓存按行组织。每条线通常非常小,通常为32个字节,每条线与其边界大小对齐。换句话说,32字节的高速缓存行将在32字节地址上对齐。对于Linux,行的大小为L1_CACHE_BYTES
,由每个体系结构定义。
地址映射到缓存行的方式因架构而异,但映射分为三个标题, 直接映射 , 关联映射 和 设置关联映射 。直接映射是最简单的方法,其中每个存储器块仅映射到一个可能的高速缓存行。使用关联映射,任何内存块都可以映射到任何缓存行。集合关联映射是一种混合方法,其中任何内存块都可以映射到任何行,但只能映射到可用行的子集中。
无论映射方案如何,它们都有一个共同点,即靠近并与高速缓存大小对齐的地址可能使用不同的行。因此Linux采用简单的技巧来尝试并最大化缓存使用
如果CPU引用不在高速缓存中的地址,则发生高速缓存未命中,并从主存储器获取数据。高速缓存未命中的成本非常高,因为对高速缓存的引用通常可以在少于10ns的时间内执行,其中对主存储器的引用通常将花费在100ns和200ns之间。然后,基本目标是尽可能多地缓存命中和尽可能少的缓存未命中。
正如某些架构不会自动管理其TLB一样,有些架构不会自动管理其CPU缓存。钩子放置在虚拟到物理映射发生变化的位置,例如在页表更新期间。 CPU缓存刷新应始终首先进行,因为当从缓存中刷新虚拟地址时,某些CPU需要存在虚拟到物理映射。
更多信息here