为啥检查没有被优化
Posted
技术标签:
【中文标题】为啥检查没有被优化【英文标题】:Why if check is not being optimized为什么检查没有被优化 【发布时间】:2017-01-06 15:12:30 【问题描述】:今天我开始使用检查两个布尔值的分支。我很确定在某些优化级别上它们只会被添加然后检查,但 gcc 和 clang 不是这种情况。为什么 gcc 不通过用加法和一个检查替换它们来优化两个布尔检查?让我给你看一个例子:
void test(bool a, bool b)
// Branch 1
if (a && b)
std::cout << "Branch 1";
// Branch 2
if (static_cast<int>(a) + static_cast<int>(b))
std::cout << "Branch 2";
gcc(即使优化级别最高)为分支 1 生成以下代码:
test dil,dil
je 400794 <test(bool, bool)+0x14>
test sil,sil
jne 4007b0 <test(bool, bool)+0x30>
它为分支 2 生成以下代码:
movzx ebx,bl
movzx ebp,bpl
add ebx,ebp
jne 4007cf <test(bool, bool)+0x4f>
两个分支(test + je)不应该比加法和分支(add + jne)慢吗?
编辑:我真正的意思是乘法,因为在真假 (1 + 0) 的情况下,加法得到真 (1),但乘法得到正确的结果 (0)。
【问题讨论】:
对于初学者来说,加法不是&&
,而是||
。此外,虽然不确定当前的 C++ 标准,但对于 false
的表示曾经是 0
,而对于 true
则表示其他所有内容。因此,您可以将-1
和+1
用于两个真值,并且将它们相加并不是很好。同样,您可以拥有1
和2
和按位and
对这些人不利。
@Jester: C++ bool
变量只能是 true
或 false
(我认为对于 C bool
值也是如此。)转换时到int
,它们必须分别转换为1或0。
加法与布尔和的行为不同。乘法就是你要找的。span>
可能与this case有关; GCC 的优化效率不如 bool
值。
你不想要乘法,@user。那会慢一些。简单的按位与有什么问题? (没什么。没什么问题。如果你想强制优化,你应该使用它。)
【参考方案1】:
在抽象机级别,&&
强制第二个表达式在第一个为假时不被计算。根据 as-if 规则,编译器可以选择评估第二个表达式——如果它可以证明它具有已定义的行为(或未定义的行为无关紧要)并且没有副作用;但是编译器作者已经明确认为这是不值得的。
如果您不想要捷径,&
可能会有所帮助(带有评论)。
【讨论】:
这不是很奇怪吗,优化器没有看到不需要短路?例如。 a && b 将产生与 a + b(或 a * b)相同的结果,因此可以使用它来代替。 @ElvissStrazdins 他们可能不会这样做,因为他们通常是这样做的。如果a
为假,a && b && c && d && e && f
将无法评估除a
之外的任何内容,而乘法则必须将所有结果一起评估/相乘。
@ElvissStrazdins:注意a && b
在所有可能的情况下都不会产生与a + b
相同的结果。考虑a = true
和b = false
。
请注意,在内部,在几个平台上,gcc 将 a&&b
替换为 a&b
早期(参见 -fdump-tree-gimple)。然后后端选择将(a&b)!=0
扩展为 2 次跳转,因为它认为平均而言这样更有效。
我还是不明白为什么两次跳跃会更快。您是否计算零扩展字节值的成本?这不是绝对必要的:你可以做test sil, dil
+je
。这怎么可能比test sil, sil
+je
+test dil, dil
+je
慢?【参考方案2】:
为什么 gcc 不通过替换它们来优化两个 bool 检查 加一检查?
建议的优化不正确。 addition 不是&&
运算符的适当替代品,因为当至少(不是两者)一个条件为true
时,前者将评估为true
.
问题仍然存在,如何优化?
C++ 标准保证bool
值转换为具有定义值的int
:false
转换为0
和true
转换为1
。因此,下面的结构是完全合法的(假设a
和b
都只是bool
变量):
if (a & b) // OK, integral promotions take place (bool ---> int)
假设编译器总是将bool
值与true
(例如0x1
)和false
(0x0
)的内部表示形式相同,则条件可以直接转换为x86 test
指令:
test sil, dil
这是棘手的部分。显然,GCC 编译器在主线 4.6 和 4.7 之间改变了它的行为。即使对int
进行显式强制转换,它也会保留两个单独的跳转。条件:
if (static_cast<int>(a) & static_cast<int>(b))
生成代码(具有-O3
优化级别的GCC 6.2.0):
test dil, dil
je .L1
test sil, sil
je .L1
另一方面,ICC 和 MSVC 2015 编译器都按位执行“真”并且:
movzx eax, BYTE PTR b$[rsp]
test al, BYTE PTR a$[rsp]
je SHORT $LN6@main
对于 4.7 版之前的 GCC(GCC 4.6.3 和 -O3
)也是如此:
movzx edi, dil
test esi, edi
【讨论】:
在我看来,GCC 和 Clang 都像 a missed optimization opportunity。正如您所指出的,ICC 和 MSVC 都正确并发出了一个简单的test
指令。有人应该为 GCC 和 Clang 提交缺陷报告。我想不出有必要独立测试这些值的单一原因,而且这肯定不是最优的。【参考方案3】:
您忘记了逻辑运算符是short-circuit evaluation。
对于逻辑与,如果左侧为假,则不计算右侧。
【讨论】:
正确但无关紧要,因为两者都是布尔值,无需“评估”,编译器可以很好地优化它。 评估顺序如何影响我的代码?他们按顺序检查或只是添加将产生相同的结果。没有办法,结果会有所不同,所以可以优化。以上是关于为啥检查没有被优化的主要内容,如果未能解决你的问题,请参考以下文章
React 为啥将函数包含在依赖项列表中被认为是一种性能优化?