繁体   English   中英

当缓存行大小通常为 64 字节时,为什么 sizeof std::mutex == 40

[英]Why is sizeof std::mutex == 40 when cache line size is often 64 bytes

在 static_assert 之后,gcc 和 clang 主干都通过了。

#include<mutex>
int main(){
    static_assert(sizeof(std::mutex)==40);
}

由于 x86 CPU 有 64 字节缓存线,我期望互斥锁 sizeof 为 64,因此可以避免错误共享。 大小“仅”40 字节是否有原因?

注意:我知道大小也会影响性能,但程序中很少有大量互斥体,因此与错误共享的成本相比,大小开销似乎可以忽略不计。

注意:有一个类似的问题问为什么 std::mutex 这么大,我问为什么它这么小:)

编辑:MSVC 16.7 的大小为 80。

在不需要的地方强制填充将是糟糕的设计。 如果用户没有任何有用的东西可以放入缓存行的其余部分,他们总是可以填充。

如果它通常是轻微竞争的,您可能希望它与它保护的数据在同一个缓存行中; 在获取锁后访问共享数据时,只有一个缓存线可以反弹,而不是第二次缓存未命中。 这可能在细粒度锁定中很常见,其中许多对象都有自己的std::mutex ,并且使它更小更有益。

(激烈的竞争可能会在试图获取锁的读者与锁所有者在获得锁的所有权后写入共享数据之间创建错误共享。在锁所有者有机会之前将缓存行翻转为“共享”或无效写,确实会减慢速度)。


或者该行其余部分的空间可能是一些很少使用的东西,需要存在于程序中的某处,但可能仅用于错误处理,因此其性能无关紧要。 如果它不能与互斥体共享一行,它就必须在其他地方占用空间。 (也许在某些“冷”数据页面中,所以这不是一个很好的例子)。

您可能不太可能想要mallocnew一个互斥锁本身,尽管一个互斥锁可能是您动态分配的类的一部分。 分配器开销是真实存在的,例如在分配簿记空间之前使用 16 字节的内存。 (glibc 的 malloc/new 的大量分配通常是页面对齐的 + 16 字节,使它们与所有更宽的边界错位)。 动态分配器簿记对于互斥体与其共享空间是一件非常好的事情:在互斥体正在使用时它可能不会被任何东西读取或写入。


非无锁std::atomic对象通常使用锁数组(可能只是简单的自旋锁,但也可能是 std::mutex)。 如果是后者,您不希望同时使用两个相邻的互斥锁,因此最好将它们打包在一起。


此外,增加它的大小将是一种非常笨拙的方式来尝试确保没有错误共享。 一个想要确保 std::mutex 自身有一个缓存行的实现,它想要用alignas(64)声明它以确保它的alignof()就是这样。 这将强制填充使 sizeof(mutex) 成为 alignof 的倍数(在这种情况下相等)。

但请注意,如果您要为其固定大小,则std::hardware_destructive_interference_size在某些现代 x86-64 上应为 128,因为英特尔 L2 缓存中的相邻行硬件预取。 这比相同的缓存行具有更弱的破坏性影响,而且浪费太多空间。

也许您的解决方案是使用 alignas? 就像是

alignas(std::hardware_destructive_interference_size) std::mutex mut;

现在您的互斥锁位于硬件边界上。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM