7

在Linux内核中,为什么很多结构都使用____cacheline_aligned_in_smp宏?访问结构时是否有助于提高性能?如果是,那怎么办?

4

3 回答 3

12

____cacheline_aligned指示编译器在对应于 L1 缓存行开头的地址处实例化一个结构或变量,以用于特定架构,即,使其与 L1 缓存行对齐。____cacheline_aligned_in_smp类似,但实际上只有当内核在 SMP 配置中编译时才对齐 L1 缓存行(即,使用 option CONFIG_SMP)。这些在文件 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 实际访问的数据只会从缓存移动到内存,反之亦然。

于 2016-03-23T19:53:37.637 回答
5

任何高速缓存(dcache 或 icache)中的每个高速缓存行都是 64 字节(在 x86 中)架构。需要缓存对齐以避免错误共享缓存行。如果缓存线在全局变量之间共享(在内核中发生更多)如果全局变量之一被其缓存中的处理器之一更改,则它将该缓存线标记为脏。在剩余的 CPU 缓存行中,它成为过时的条目,需要刷新并从内存中重新获取。这可能会导致高速缓存行未命中,这需要更多的 CPU 周期。这会降低系统的性能。请记住,这是针对全局变量的。大多数内核数据结构器都使用它来避免缓存行未命中。

于 2014-09-23T07:15:32.483 回答
3

Linux 以与 TLB 非常相似的方式管理 CPU 缓存。CPU 缓存与 TLB 缓存一样,利用了程序倾向于表现出引用局部性这一事实。为了避免每次引用都必须从主内存中获取数据,CPU 将在 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 需要存在虚拟到物理的映射。

更多信息在这里

于 2014-11-28T10:24:45.037 回答