如何让两个类成员处于同一个缓存行(cache line)中?
缓存行对齐与结构体成员排布原理
开头填充保证前两个成员同缓存行的原理
开头的填充可以让buffer_和buffer_mask_处于同一条缓存行,而非拆分到两行,避免访问时产生双倍内存流量。
当前主流CPU的缓存行大小通常为64字节,CPU访问内存时以缓存行为单位加载数据到缓存中,若访问的数据跨两个缓存行存储,需要触发两次内存加载操作,访存开销直接翻倍。
若结构体未做缓存行对齐处理,分配到的起始地址可能落在某个缓存行的中间位置,此时如果前两个成员的总长度超过当前缓存行的剩余空间,就会被拆分到两个缓存行中。在结构体开头添加对应大小的填充,本质是将整个结构体的对齐属性提升到和缓存行大小一致,保证结构体起始地址必然对齐到缓存行的起始位置,只要前两个成员的总大小不超过缓存行大小,就可以保证二者完全处于同一缓存行,避免拆分。
举个实际例子:假设64字节缓存行,结构体前两个成员分别是8字节指针buffer_和8字节整数buffer_mask_,总大小16字节远小于缓存行大小:
- 若结构体未对齐,起始地址为
0x1003C(对应缓存行第60字节位置),buffer_的地址范围为0x1003C ~ 0x10043,跨0x10000~0x1003F和0x10040~0x1007F两个缓存行,buffer_mask_地址范围为0x10044 ~ 0x1004B,完全落在第二个缓存行,此时访问两个成员需要加载两次缓存行,产生双倍流量。 - 若结构体开头添加填充将对齐属性提升到64字节对齐,结构体起始地址必然为
0x10040这类64字节对齐地址,buffer_和buffer_mask_的地址范围都落在0x10040 ~ 0x1004F,完全在同一个缓存行,仅需一次加载即可访问两个成员。
结构体中间成员的同缓存行实现方法
如果需要保证两个位于结构体中间的成员处于同一缓存行,核心思路是让两个目标成员中的第一个对齐到缓存行起始位置,且两个成员总大小不超过缓存行大小,有两种常见实现方案:
- 方案1:手动插入填充
在两个目标成员的前一个成员之后手动插入填充字节,保证第一个目标成员的偏移量为缓存行大小的整数倍。示例代码如下(缓存行大小按64字节计算):
#include <stddef.h> struct demo { int prev1; char prev2; // 手动插入填充,保证下面a的偏移对齐到64字节 char pad[64 - (offsetof(struct demo, a) % 64)]; long a; // 第一个目标成员 long b; // 第二个目标成员 float next; };
- 方案2:使用编译器对齐属性
直接用编译器提供的对齐属性修饰第一个目标成员,编译器会自动在该成员之前插入足够的填充,保证该成员地址对齐到缓存行起始位置。示例代码如下:
struct demo { int prev1; char prev2; // 标记a对齐到64字节边界,编译器自动补全填充 long __attribute__((aligned(64))) a; long b; float next; };
注意:如果是动态分配的结构体,需要保证整个结构体的分配地址至少对齐到缓存行大小,否则对齐属性可能失效,可以使用
memalign、aligned_alloc等对齐分配函数实现。
内容的提问来源于stack exchange,提问作者victor
相关产品推荐
相关产品推荐

