16字节atomic<>
变量是否在16字节边界上自动对齐,从而允许编译器/运行时库有效地使用x86 CMPXCHG16B
指令吗?还是我们应该总是为所有此类变量手动指定alignas(16)
?
如果库完全使用std::atomic<>
而不是16字节对象的互斥量,则alignas
的任何体面实现都将使用lock cmpxchg16b
本身来提高lock cmpxchg16b
的效率。
[并非所有实现都可以,例如,我认为MSVC的标准库使用标准互斥锁回退使16字节对象完全无锁。
您不需要alignas(16)
上的atomic<T>
。
[如果您要使用T
的普通atomic_ref
对象,则只需要对原子进行手动对齐。 atomic_ref<>
没有对齐现有T对象的机制。设计的当前版本公开了您应该使用的required_alignment
成员。这样做是为了您的正确性。 (否则,您得到的UB可能意味着撕裂,或者对于拆分lock
RMW而言,只是[[extremely降低了系统范围的性能。)]
// 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
拆分仍然是原子的,但是非常慢(与任何lock
ed指令相同:原子RMW的原子性保证不取决于对齐)。更像是实际的总线锁定,而不仅仅是本地到此内核cache lock delaying MESI responses。窄原子绝对需要自然对齐以确保正确性,因为纯负载和纯存储可以编译为asm pure load or store where HW guarantees require some alignment。
但是16字节对象只能通过
lock cmpxchg16b
保证是原子的,因此.load()
和.store()
必须通过lock cmpxchg16b
实现。 (使用CAS(0,0)加载以获取旧值,或者将0替换为自身或不执行任何操作,然后使用CAS重试循环进行存储。这很烂,但比互斥锁要好一些。它没有read-您希望从无锁load
获得的可扩展性,这是GCC7和更高版本不再将atomic<16-byte-object>
宣传为无锁的原因之一,尽管它仍会在调用的libatomic函数中使用lock cmpxchg16b
而不是内联lock cmpxchg16b
)