![]() |
1
13
除了性能,标准没有 保证 你能说的任何方式;这或多或少就是问题所在。
如果您愿意引入一些特定于平台的UB,您可以执行以下操作
如果此测试成功(即,纯C++类型自然是原子的,只有
在任何特定的平台/目标体系结构上,您都可以在调试器中单步执行代码,并查看asm指令运行的内容。(包括进入libatomic函数调用,如
(有趣的事实:
gcc7 with statically linked libatomic
可能总是对x86-64上的16字节对象使用锁定,因为它没有机会在动态链接时进行运行时CPU检测并使用
您可以通过可移植的方式查找性能差异(例如,具有多个读卡器的可扩展性),但x86-64
这也是gcc7决定停止为返回true的部分原因
还要注意,32位x86上的clang使用
(在C11中,在对象中包含锁的范围要小得多:它必须在最小的初始化条件下工作(例如静态为0),并且没有析构函数。编译器/ABI通常需要它们的C
通常的机制是使用原子对象的地址作为锁的全局哈希表的密钥 .两个对象别名/冲突和共享同一个锁是额外的争用,但不是正确性问题。这些锁仅从库函数中获取/释放,而不是在持有其他此类锁时获取/释放,因此它不会创建死锁。 您可以通过在两个不同的进程之间使用共享内存来检测这一点(因此每个进程都有自己的锁哈希表)。 Is C++11 atomic<T> usable with mmap?
从C++11标准来看,其目的是对于无锁对象,这仍然是原子的。它也可能适用于非无锁对象(如果它们在对象中嵌入锁),这就是为什么您必须通过检查
如果您看到两个进程之间发生撕裂,则表示对象没有锁定 (至少不是C++11的预期方式,也不是普通共享内存CPU的预期方式。) 如果进程不必共享除包含原子对象的1页之外的任何地址空间,我不知道为什么无地址重要 2. (当然,C++11根本不要求实现使用页面。或者,实现可以将锁的哈希表放在每个页面的顶部或底部?在这种情况下,使用依赖于页面偏移量上方的地址位的哈希函数是完全愚蠢的。) 无论如何 这取决于关于计算机如何工作的许多假设,这些假设在所有普通CPU上都是正确的,但C++无法做到这一点。 如果您关心的实现是在主流CPU(如x86或ARM)上的普通操作系统下进行的,那么这种测试方法应该相当准确,并且可能是一种替代读取asm的方法。 这不是在编译时自动完成的非常实用的事情,但它将是 可能的 自动化这样的测试并将其放入构建脚本中,这与读取asm不同。 脚注1:x86上的16字节原子
没有x86硬件文档支持带SSE指令的16字节原子加载/存储
.实际上,许多现代CPU都有原子
看见
SSE instructions: which CPUs can do atomic 16B memory operations?
对于一个棘手的情况:在K10上的测试表明,对齐的xmm加载/存储在同一个套接字上的线程之间没有撕裂,但不同套接字上的线程经历了罕见的撕裂,因为HyperTransport显然只提供了8字节对象的最小x86原子性保证。(IDK如果
如果没有供应商发布的保证,我们也无法确定奇怪的微体系结构转角情况。在一个简单的测试中,一个线程写入模式,另一个线程读取模式,这是很好的证据,但在某些特殊情况下,CPU设计者决定以不同于正常的方式处理某些事情,这总是有可能的。
只读访问只需要指针的指针+计数器结构可能很便宜,但当前编译器需要
64位模式下的ILP32 ABI(32位指针)(如 Linux's x32 ABI ,或AArch64的ILP32 ABI)意味着指针+整数只能容纳8个字节,但整数寄存器仍有8个字节宽。这使得使用指针+计数器原子对象比在指针为8字节的64位模式下更有效。 脚注2:无地址 我认为术语“无地址”与不依赖于任何每个进程状态是一个单独的声明。据我所知,这意味着正确性并不取决于两个线程对相同的内存位置使用相同的地址。但是,如果正确性还取决于它们共享相同的全局哈希表(IDK为什么将对象的地址存储在对象本身中会有帮助),那么只有在同一进程中可能有同一对象的多个地址时,这才有关系。那个 是 可以在类似x86的实模式分段模型上使用,其中20位线性地址空间使用32位段:偏移量进行寻址。(16位x86的实际C实现向程序员公开了分段;将其隐藏在C规则后面是可能的,但性能不高。) 虚拟内存也是可能的:在同一进程中,相同物理页到不同虚拟地址的两个映射是可能的,但很奇怪。这可能使用也可能不使用相同的锁,这取决于哈希函数是否使用页面偏移量以上的任何地址位。 (表示页面内偏移量的地址低位对于每个映射都是相同的。即,这些位的虚拟到物理转换是不可操作的,这就是为什么 VIPT caches are usually designed to take advantage of that to get speed without aliasing .) 因此,即使使用单独的全局哈希表而不是向原子对象添加互斥体,非无锁对象在单个进程中也可能是无地址的。但这将是一种非常不寻常的情况;使用虚拟内存技巧在 相同的 在线程之间共享其所有地址空间的进程。更常见的是进程之间共享内存中的原子对象。(我可能误解了“无地址”的含义;它可能意味着“无地址空间”,即不依赖于共享的其他地址。) |
![]() |
2
2
我认为您实际上只是想检测gcc特定的特殊情况
在这种情况下,作为一个实际问题,我只需编写您的检测函数来硬编码您知道的gcc版本范围以这种方式运行。当前,更改为停止内联的版本之后的所有版本
所以像这样的事情应该是一个好的开始:
这里是
我认为这种方法是有效的,因为
真正无锁
没有很好的定义:在这种情况下,您已经决定要考虑
这个想法的基础是,得到错误的答案不会是一个破坏世界的功能问题,而是一个性能问题:我猜您正在尝试进行此检测,以选择替代实现,其中一个在“真正的”无锁系统上更快,另一个在以下情况下更合适
如果您的需求更强大,并且确实想变得更健壮,那么为什么不将这些方法结合起来:使用这个简单的版本检测方法和 合并它 使用运行时/编译时检测方法,按照Peter的回答检查撕裂行为或反编译。如果两种方法都一致,请将其作为您的答案;但是,如果他们不同意,则应揭露错误并进行进一步调查。这也将有助于您抓住gcc更改实现以使16字节对象锁满的关键点(如果有的话)。 |
![]() |
Rasim Avcı · 如何履行承诺。解析需要原子化的代码 6 年前 |
![]() |
Lingxi · 真正测试std::atomic是否无锁 6 年前 |
![]() |
Rajeev Mehta · 修改和读取原子变量 7 年前 |
![]() |
Chris Jefferson · 在Rust中获得“无序”语义 7 年前 |
![]() |
Jérôme B · redis自动切换值 7 年前 |
![]() |
krimog · 更新where select,保证原子性 7 年前 |