为啥标准库不以无锁方式为 8 字节以下的结构实现 std::atomic?
Posted
技术标签:
【中文标题】为啥标准库不以无锁方式为 8 字节以下的结构实现 std::atomic?【英文标题】:Why don't standard libraries implement std::atomic for structs under 8 bytes in a lock-free manner?为什么标准库不以无锁方式为 8 字节以下的结构实现 std::atomic? 【发布时间】:2019-09-17 13:56:57 【问题描述】:假设体系结构可以为 std::atomic 以无锁方式支持 8 字节标量。为什么标准库不为 8 字节以下的结构提供类似的特化?
这种 std::atomic 特化的简单实现可以将结构体序列化/反序列化(使用std::memcpy
)成等效的std::uintx_t
,其中x
是结构体的宽度(以位为单位)(四舍五入到大于或等于结构宽度的 2 的最接近幂)。这将是很好的定义,因为 std::atomic 要求这些结构可以轻松复制。
例如。 https://godbolt.org/z/sxSeId,这里Something
只有3个字节,但是实现调用__atomic_load
和__atomic_exchange
,都使用了锁表。
【问题讨论】:
gcc 如果您将结构设为 4 个字节(但不是 3 个字节),则它会正确,请参阅godbolt.org/z/d1OCmG。铿锵声没有。 @PaulSanders 有趣,我想知道为什么 3 个字节不起作用.. 没有 x86 指令可以加载/存储 3 个字节,更不用说原子了。 @rustyx 啊,对不起,但你总是可以占用超过 2 的下一个幂的大小,对吧?§[atomics.types.generic]p3
部分允许这样做 - 原子特化的表示不需要与其相应的参数类型具有相同的大小。我猜这有可移植性问题?
@Curious:当我说“强制对齐”时,我指的是alignas(4)
。
【参考方案1】:
Linux 的 atomic<T>
不幸的是(?)不对齐 / 填充到 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;
,因为这会使 each 字符填充到 4 个字节,以便它们每个都可以对齐。)
具有非 2 次幂大小的对象可能跨越缓存线边界,因此并非总是可以使用更宽的 4 字节负载。
另外,纯存储总是必须使用 CAS(例如lock cmpxchg
)来避免发明写入对象外部的字节:显然你不能使用两个单独的mov
存储(2 字节 + 1 字节) 因为这不是原子的,除非您在 TSX 事务中使用重试循环执行此操作。
x86 加载/存储仅保证不跨越 8 字节边界的内存访问是原子的。 (在某些供应商/uarches 上,缓存线边界。或者对于可能无法缓存的加载/存储,基本上自然对齐是您所需要的)。 Why is integer assignment on a naturally aligned variable atomic on x86?
您的struct Something char a, b, c; ;
没有对齐要求,因此没有阻止Something
对象跨越2 个缓存行的C++ 规则。这将使它的普通-mov
加载/存储绝对不是原子的。
gcc 和 clang 选择使用与 T
相同的布局/对象表示来实现 atomic<T>
(无论是否无锁)。因此atomic<Something>
是一个 3 字节的对象。因此,atomic<Something>
的数组必然有一些跨越高速缓存行边界的对象,并且不能在对象外部进行填充,因为这不是数组在 C 中的工作方式。sizeof()
= 3 告诉您数组布局。 这使得无锁 atomic<Something>
成为不可能。(除非您使用 lock cmpxchg
加载/存储即使在缓存行拆分时也是原子的,否则在这样做的情况下会产生巨大的性能损失发生。最好让开发人员修复他们的结构。)
atomic<T>
类可以比T
有更高的对齐要求,例如atomic<int64_t>
有 alignof(atomic_int64_t) == 8,这与许多 32 位平台上的alignof(int64_t) == 4
不同(包括 i386 System V ABI )。
如果 gcc/clang 没有选择保持布局相同,他们可以让 atomic<T>
填充小对象到 2 的下一个幂并添加对齐,以便它们可以无锁。这将是一个有效的实施选择。我想不出任何缺点。
有趣的是,gcc 的 C11 _Atomic
支持是 slightly broken on 32-bit platforms with 64-bit lockless atomics :_Atomic int64_t
可能在结构内部错位导致撕裂。他们仍然没有更新 _Atomic
类型的 ABI 以实现自然对齐。
但是 g++ 的 C++11 std::atomic 在头文件中使用了一个模板类,不久前修复了该错误 (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65147);确保 atomic<T>
具有自然对齐(最大为 2 大小的幂),即使 T
具有对齐
【讨论】:
哦,缓存线拆分确实很有意义,谢谢!以上是关于为啥标准库不以无锁方式为 8 字节以下的结构实现 std::atomic?的主要内容,如果未能解决你的问题,请参考以下文章