Intel CPU架构支持不对齐的内存访问。Intel将跨缓存行(cache line)的原子操作称为Split Lock。在并发或高性能计算场景中,频繁的Split Lock会影响系统性能,并可能导致系统卡顿或崩溃。
工作原理
当一个原子操作的操作数跨越两个cache line时,为确保这类操作的原子性,处理器会锁定总线,强制所有其他核心暂停内存访问,直到操作完成。
Split Lock的发生具备两个特征:
原子操作:执行带
LOCK
前缀的汇编指令。跨缓存行访问:操作数地址未对齐,跨两个缓存行(Cache Line)。
Split Lock的影响
全局性能下降:Split Lock会阻塞内存访问,降低系统中所有进程的性能,而不仅限于触发它的进程。
延迟增长:频繁的Split Lock会增加内存访问延迟,并引起系统性能抖动。
检测Split Lock
以ecs.g8i.xlarge为例,如果counts
列的数值大于0,则表明系统中存在触发Split Lock的操作。
perf stat -e cpu/event=0x2c,umask=0x10/ -a -I 1000
若内存访问未跨越缓存行(cache line
),则对应的计数值为0。
规避Split Lock
确保原子变量对齐到自然边界。
// 推荐:64 字节对齐,避免跨行和伪共享 alignas(64) atomic<uint64_t> counter; // 针对 128 位原子类型,16 字节对齐 alignas(16) atomic<__int128> big_counter;
避免将大原子变量放置在结构体中间。
// 不推荐:原子变量可能因前面的成员而发生位移,导致跨行 struct BadExample { char a; // 占用 1 字节 atomic<__int128> val; // 可能跨缓存行 }; // 推荐:将对齐要求最高的成员放在最前,并显式声明 struct GoodExample { alignas(16) atomic<__int128> val; char a; };
使用更小的原子类型组合替代大原子。对于不需要真正128位原子性的场景,可拆分为两个64位原子操作。
struct PaddedCounter { alignas(64) atomic<uint64_t> low; alignas(64) atomic<uint64_t> high; };
不要使用未对齐指针进行原子操作。
//错误:malloc 不保证 16 字节对齐(尤其老 libc) void* ptr = malloc(sizeof(atomic<__int128>)); atomic<__int128>* p = new(ptr) atomic<__int128>; //正确: 使用 aligned_alloc void* aligned_ptr = aligned_alloc(16, sizeof(atomic<__int128>)); atomic<__int128>* p = new(aligned_ptr) atomic<__int128>;
使用
static_assert
检查对齐。static_assert(alignof(atomic<__int128>) >= 16, "128-bit atomic must be 16-byte aligned");
避免在packed结构体中使用原子类型。
#pragma pack(push, 1) struct Packed { uint8_t flag; atomic<uint64_t> counter; // 错误:8字节也可能因紧凑布局跨行 }; #pragma pack(pop)
该文章对您有帮助吗?