GCC 编译器中的条件移动 (cmov)
Posted
技术标签:
【中文标题】GCC 编译器中的条件移动 (cmov)【英文标题】:Conditional move (cmov) in GCC compiler 【发布时间】:2020-02-01 17:51:37 【问题描述】:我在某处看到 GCC 编译器在将我的代码转换为 ASM 时有时可能不喜欢使用条件 mov。
在哪些情况下它可能会选择执行条件 mov 以外的操作?
【问题讨论】:
我不知道 GCC 的细节。但有关一般信息,请参阅 Agner Fog 的x86 optimization guide 中的用条件移动替换条件跳跃。 【参考方案1】:当分支的两边都很短时,编译器通常倾向于将 if-conversion 转换为 cmov,尤其是对于三元组,因此您总是分配一个 C 变量。例如if(x) y=bar;
有时不会针对 CMOV 进行优化,但 y = x ? bar : y;
确实更频繁地使用 CMOV。尤其是当y
是一个否则不会被触及的数组条目时:引入它的非原子 RMW 可能会创建源中不存在的数据竞争。 (编译器无法发明对可能共享对象的写入。)
if 转换合法但显然无利可图的一个明显例子是当 if/else 的两边都有大量工作时。例如一些乘法和除法,整个循环和/或表查找。即使 gcc 可以证明两边都运行并在最后选择一个结果是安全的,它也会看到做更多的工作不值得避免一个分支。
If 转换为数据依赖(无分支 cmov)甚至仅在有限的情况下才有可能。例如Why is gcc allowed to speculatively load from a struct? 显示了可以/不能完成的情况。其他情况包括进行 C 抽象机没有的内存访问,编译器无法证明它不会出错。或者可能有副作用的非内联函数调用。
另请参阅这些关于让 gcc 使用 CMOV 的问题。
Getting GCC/Clang to use CMOV how to force the use of cmov in gcc and VS Make gcc use conditional moves另请参阅Disabling predication in gcc/g++ - 显然gcc -fno-if-conversion -fno-if-conversion2
将禁用 cmov。
对于 cmov 损害性能的情况,请参阅 gcc optimization flag -O3 makes code slower than -O2 - GCC -O3
需要配置文件引导优化以使其正确,并为 if
使用分支,结果证明这是高度可预测的。 GCC -O2
首先没有进行 if 转换,即使没有 PGO 分析数据。
另一个例子:Is there a good reason why GCC would generate jump to jump just over one cheap instruction?
GCC seemingly misses simple optimization 显示了三元在两半都有副作用的情况:三元不像 CMOV:甚至只有一侧被评估为副作用。
AVX-512 and Branching 显示了一个 Fortran 示例,其中 GCC 需要源代码更改的帮助才能使用无分支 SIMD。 (相当于标量 CMOV)。这是不发明写入的情况:对于源不会写入的元素,它不能将读取/分支转换为读取/可能修改/写入。 自动矢量化通常需要If-conversion。
【讨论】:
编译器无法发明对可能共享对象的写入 - 编译器不认为只有写入std::atomic
变量才是写入共享变量吗?仅当不共享该变量时,对非原子变量的写入才是无数据竞争的,不是吗?
@MaximEgorushkin:如果这个线程从不接触arr[5]
(在C抽象机中),只有arr[4]
和arr[6]
,那么引入arr[5]
的非原子RMW不是允许。当我们的循环运行时,其他一些线程可能正在写它,我们可以踩到那个写。发明写入可以创建源中不存在的“共享”(从而导致数据竞争),因此是不允许的。 (通常也不允许将读取转换为 RMW,即使我们正在读取,那么另一个线程正在写入已经是 UB。如果没有硬件数据竞争检测,理论上可能没问题?)
@PeterCordes - 另一个不涉及并发的参数是如果内存映射为只读会发生什么?编译器在实践中会这样做(例如,字符串常量),如果您尝试编写它们,即使使用相同的值也会崩溃,因此这是一种简单的方法来表明在不调用并发的情况下这是不允许的(这是一个更冗长的“证明”)。
@BeeOnRope:是的,但是如果编译器可以证明数组的任何部分都被写入,那么整个东西就不可能在只读内存中。或者至少是一个结构。或者数组的mprotect
的一部分是合法的(不是UB),所以它的一部分是const
,但其余的不是?可能对于像 GCC 这样的真正编译器的相当严格的不发明写入行为,这很好。现实世界的编译器可能不希望发明写入,因为它可能会不必要地破坏 COW 共享。
所以我认为问题更多的是编译器想要支持什么。我猜他们会支持不逐个元素地发明写入(即,将支持 mprotect 场景)。请注意,icc 确实发明了写入!我可以通过将const char *
定义的字符串传递给由 icc 使用发明写入向量化的方法来可靠地使其崩溃。以上是关于GCC 编译器中的条件移动 (cmov)的主要内容,如果未能解决你的问题,请参考以下文章