提问者:小点点

优化时编译器是否需要关心其他线程?


这是关于C#线程安全保证的讨论的副产品。

我有以下假设:

如果没有线程感知的原语(互斥锁、std::atomic*等,为了简单起见,我们也排除易失性),如果当前线程中代码的语义学(即输出和[在本问题中排除]易失性访问)从当前线程的角度来看保持不变,即忽略其他线程的存在,有效的C编译器可以进行任何类型的转换,包括从内存中引入读取(或例如,如果它愿意写入)。引入读/写可能会改变其他线程的行为(例如,因为其他线程在没有适当同步或执行其他类型UB的情况下读取数据)的事实可以被符合标准的编译器完全忽略。

这个假设正确与否?我希望这遵循假设规则。(我相信是,但其他人似乎不同意我的观点。)如果可能,请包括适当的规范性参考。


共1个答案

匿名用户

是的,当并非所有访问都被读取时,C将数据竞争UB定义为对非原子对象的潜在并发访问。另一个最近的问题

[intr.种族]/2-如果其中一个表达式计算修改了内存位置…而另一个读取或修改了相同的内存位置,则两个表达式计算会发生冲突。

[intr. ran]/21…如果程序的执行包含两个潜在的并发冲突动作,其中至少一个不是原子的,并且两者都不会在另一个之前发生,则程序的执行包含数据竞争,…

任何此类数据竞争都会导致未定义的行为。

这使得编译器可以自由地优化代码,以保持执行函数的线程的行为,而不是其他线程(或调试器)在查看不应该查看的内容时可能会看到的行为。(即数据竞争UB意味着读取/写入非原子变量的顺序不是优化器必须保留的可观察行为的一部分。)

引入读/写可能会改变其他线程的行为

假设规则允许您发明读取,但不可以,您不能发明对该线程尚未编写的对象的写入。这就是为什么if(a[i]

两个不同的线程同时编写a[1]a[2]是合法的,一个线程加载a[0…3]然后存储一些修改和一些未修改的元素可能会被编写a[2]的线程踩在商店上。

与icc崩溃:编译器可以在抽象机器中不存在的地方发明写入吗?详细介绍了编译器bugICC在使用SIMD混合进行自动矢量化时所做的工作。包括Herb Sutter原子武器演讲的链接,他在其中讨论了编译器不得发明写入的事实。

相比之下,AVX-512掩码和AVXvmaskmovps等,如ARMSVE和RISC-V向量扩展,我认为,确实有适当的掩码和故障抑制,实际上根本不存储到一些SIMD元素,没有分支。

>

  • 在AVX-512加载和存储时使用掩码寄存器时,是否会因对掩码元素的无效访问而引发故障?AVX-512掩码确实对掩码元素扩展到的只读或未映射页面进行了故障抑制。

    AVX-512和分支-使用if()与无分支中的存储进行自动向量化。

    发明原子RMW是合法的(除了没有修改部分),例如一个8字节的锁cmpxchg[rcx], rdx,如果你想修改该区域中的一些字节。但实际上,这比单独存储修改后的字节更昂贵,因此编译器不会这样做。

    当然,无条件写入a[2]的函数可以多次写入,并且在最终将其更新为最终值之前使用不同的临时值。(可能只有Deathstation 9000会发明不同值的临时内容,例如将a[2]=3转换为a[2]=2; a[2];

    有关编译器可以合法做什么的更多信息,请参阅谁害怕一个大的糟糕的优化编译器?在LWN上。那篇文章的上下文是Linux内核开发,它们依赖GCC超越ISOC标准,并且实际上以理智的方式运行,使得使用易失性int*和内联asm滚动自己的原子成为可能。它解释了阅读或编写非原子共享变量的许多实际危险。

  • 相关问题