为什么标准库不以无锁方式为 8 字节以下的结构实现 std::atomic?

Why don't standard libraries implement std::atomic for structs under 8 bytes in a lock-free manner?

假设架构可以以无锁方式支持 8 字节标量 std::atomic。为什么标准库不为小于 8 字节的结构提供类似的专业化?

这种 std::atomic 特化的简单实现可以 serialize/deserialize (使用 std::memcpy)将结构转换为等价的 std::uintx_t,其中 x 是以位为单位的结构宽度(四舍五入为大于或等于结构宽度的最接近的 2 的幂)。这将被很好地定义,因为 std::atomic 要求这些结构可以被简单地复制。

例如。 https://godbolt.org/z/sxSeId,这里的Something只有3个字节,但是实现调用了__atomic_load__atomic_exchange,都使用了锁table。

不幸的是,

Linux 的 atomic<T>(?) 没有对齐/填充到 power-of-2 大小。 std::atomic<Something> arr[10] 有 sizeof(arr) = 30。(https://godbolt.org/z/WzK66xebr)


使用struct Something { alignas(4) char a; char b,c; };
(不是 alignas(4) char a,b,c;,因为那样会使 每个 字符填充到 4 个字节,这样它们每个都可以对齐。)

Objects 具有非 power-of-2 大小可能跨越 cache-line 边界 因此使用更宽的 4 字节加载是并非总是可能。

加上纯存储总是必须使用 CAS(例如 lock cmpxchg)来避免发明写入 object 之外的字节:显然你不能使用两个单独的 mov 存储(2 字节 + 1 字节),因为那不是原子的,除非您在 TSX 事务中使用重试循环执行此操作。


x86 load/store 只保证不跨越 8 字节边界的内存访问是原子的。 (在某些供应商/uarches 上,缓存行边界。或者对于 possibly-uncacheable loads/stores,基本上自然对齐就是您所需要的)。 Why is integer assignment on a naturally aligned variable atomic on x86?

您的 struct Something { char a, b, c; }; 没有对齐要求,因此没有 C++ 规则阻止 Something object 跨越 2 个缓存行。这将使它成为一个简单的mov load/store non-atomic.

gcc 和 clang 选择使用与 T 相同的布局/object-representation 实现 atomic<T>(无论是 lock-free 或不)。因此 atomic<Something> 是一个 3 字节的 object。因此,atomic<Something> 的数组必然有一些 object 跨越缓存行边界,并且不能在 object 之外填充,因为这不是数组在 C 中的工作方式。 sizeof() = 3 告诉你数组布局。 这使得 lock-free atomic<Something> 不可能。(除非你 load/store 和 lock cmpxchg 即使在 cache-line 分裂时也是原子的,在确实发生这种情况的情况下,这将产生巨大的性能损失。最好让开发人员修复他们的结构。)

atomic<T>class可以比T有更高的对齐要求,比如atomic<int64_t>有alignof(atomic_int64_t)==8,不像alignof(int64_t) == 4 在许多 32 位平台上(包括 i386 System V ABI)。

如果 gcc/clang 没有选择保持布局不变,他们本可以 atomic<T> 填充小 objects 到 2 的下一个幂并添加对齐,因此它们可以是 lock-free。那将是一个有效的实施选择。我想不出任何缺点。


有趣的是,gcc 的 C11 _Atomic 支持是 slightly broken on 32-bit platforms with 64-bit lockless atomics_Atomic int64_t 可能在结构内部未对齐导致撕裂。他们仍然没有更新 _Atomic 类型的 ABI 以具有自然对齐。

但是 g++ 的 C++11 std::atomic 在 header 中使用了一个模板 class,该模板不久前修复了该错误 (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65147);确保 atomic<T> 具有自然对齐(最大为 2 的某个幂),即使 T 的对齐小于尺寸。因此,它们无法跨越任何比它们更宽的边界。