【发布时间】:2020-05-25 01:07:47
【问题描述】:
16 字节 atomic<> 变量是否自动在 16 字节边界上对齐,从而允许编译器/运行时库有效地使用 x86 CMPXCHG16B 指令?或者我们应该始终手动为所有此类变量指定alignas(16)?
【问题讨论】:
标签: c++ x86-64 memory-alignment stdatomic compare-and-swap
16 字节 atomic<> 变量是否自动在 16 字节边界上对齐,从而允许编译器/运行时库有效地使用 x86 CMPXCHG16B 指令?或者我们应该始终手动为所有此类变量指定alignas(16)?
【问题讨论】:
标签: c++ x86-64 memory-alignment stdatomic compare-and-swap
如果库完全使用 lock cmpxchg16b 而不是用于 16 字节对象的互斥锁,那么 std::atomic<> 的任何体面实现都将使用 alignas 本身来提高 lock cmpxchg16b 的效率。
并非所有实现都这样做,例如我认为 MSVC 的标准库使用标准互斥量回退使 16 字节对象完全非无锁。
atomic<T> 上不需要 alignas(16)。
如果你有一个普通的T 对象,你想在上面使用atomic_ref,你只需要手动对齐原子。 atomic_ref<> 没有对齐已经存在的 T 对象的机制。当前版本的设计公开了您应该使用的required_alignment 成员。为了正确性,您可以这样做。 (否则你会得到 UB,这可能意味着撕裂,或者只是非常对于 split lock RMWs 会降低系统范围的性能。)
// for atomic_ref<T>
alignas(std::atomic_ref<T>::required_alignment) T sometimes_atomic_var;
// often equivalent, and doesn't require checking that atomic_ref<T> is supported
alignas(std::atomic<T>) T sometimes_atomic_var;
// use the same alignment as atomic<T>
请注意,跨越缓存行边界的未对齐lock cmpxchg16b 拆分仍然是原子的,但非常慢(与任何locked 指令相同:原子RMW 的原子性保证不取决于对齐)。更像是一个真正的总线锁,而不仅仅是一个本地到这个核心的cache lock delaying MESI responses。
为了正确性,更窄的原子肯定需要自然对齐,因为 pure-load 和 pure-store 可以编译为 asm pure load or store where HW guarantees require some alignment。
但是 16 字节对象只能通过 lock cmpxchg16b 保证是原子的,所以 .load() 和 .store() 必须用 lock cmpxchg16b 实现。 (使用 CAS(0,0) 加载以获取旧值,然后将 0 替换为自身或不执行任何操作,并使用 CAS 重试循环进行存储。这很糟糕,但比互斥锁要好一些。它没有读取 -您期望从无锁 load 获得的侧面可扩展性,这是 GCC7 和以后不再将 atomic<16-byte-object> 宣传为无锁的原因之一,即使它仍将在它调用的 libatomic 函数中使用 lock cmpxchg16b 而不是内联lock cmpxchg16b)
【讨论】:
static_assert(alignof(std::atomic<...>) >= sizeof(std::atomic<...>));
SIGBUS 发送到尝试获取拆分锁的进程。 git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/…
sq_misc.split_lock。但是,是的,正如内核补丁所指出的那样,包括其他内核在内的情况非常非常糟糕,以至于引入了其他硬件功能来检测它。术语说明:锁不是您“获取”的对象,它是访问权限的修饰符。这是关于带有 lock 前缀的指令,这些指令执行一个原子事务。
atomic_ref 可能需要手动指定对齐方式,因为它无法控制其 external 基础变量。 atomic 的下降实现甚至会将 11 字节的底层类型填充到 16。
atomic_ref<T> 包括required_alignment 成员。我之前的措辞就是基于此;更新。要么我错过了它,要么标准委员会后来才注意到原子性有时需要超过alignof(T),并且没有人希望编译器发出在运行时检查对齐以决定特定对象是否为无锁的代码。 (即每个人都讨厌.is_lock_free(),喜欢.is_always_lock_free)