为啥检查没有被优化

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)。

【问题讨论】:

对于初学者来说,加法不是&amp;&amp;,而是||。此外,虽然不确定当前的 C++ 标准,但对于 false 的表示曾经是 0,而对于 true 则表示其他所有内容。因此,您可以将-1+1 用于两个真值,并且将它们相加并不是很好。同样,您可以拥有12 和按位and 对这些人不利。 @Jester: C++ bool 变量只能是 truefalse (我认为对于 C bool 值也是如此。)转换时到int,它们必须分别转换为1或0。 加法与布尔和的行为不同。乘法就是你要找的。​​span> 可能与this case有关; GCC 的优化效率不如 bool 值。 你不想要乘法,@user。那会慢一些。简单的按位与有什么问题? (没什么。没什么问题。如果你想强制优化,你应该使用它。) 【参考方案1】:

在抽象机级别,&amp;&amp; 强制第二个表达式在第一个为假时不被计算。根据 as-if 规则,编译器可以选择评估第二个表达式——如果它可以证明它具有已定义的行为(或未定义的行为无关紧要)并且没有副作用;但是编译器作者已经明确认为这是不值得的。

如果您想要捷径,&amp; 可能会有所帮助(带有评论)。

【讨论】:

这不是很奇怪吗,优化器没有看到不需要短路?例如。 a && b 将产生与 a + b(或 a * b)相同的结果,因此可以使用它来代替。 @ElvissStrazdins 他们可能不会这样做,因为他们通常是这样做的。如果a 为假,a &amp;&amp; b &amp;&amp; c &amp;&amp; d &amp;&amp; e &amp;&amp; f 将无法评估除a 之外的任何内容,而乘法则必须将所有结果一起评估/相乘。 @ElvissStrazdins:注意a &amp;&amp; b 在所有可能的情况下都不会产生与a + b 相同的结果。考虑a = trueb = false 请注意,在内部,在几个平台上,gcc 将 a&amp;&amp;b 替换为 a&amp;b 早期(参见 -fdump-tree-gimple)。然后后端选择将(a&amp;b)!=0 扩展为 2 次跳转,因为它认为平均而言这样更有效。 我还是不明白为什么两次跳跃会更快。您是否计算零扩展字节值的成本?这不是绝对必要的:你可以做test sil, dil+je。这怎么可能比test sil, sil+je+test dil, dil+je慢?【参考方案2】:

为什么 gcc 不通过​​替换它们来优化两个 bool 检查 加一检查?

建议的优化不正确。 addition 不是&amp;&amp; 运算符的适当替代品,因为当至少(不是两者)一个条件为true 时,前者将评估为true .

问题仍然存在,如何优化

C++ 标准保证bool 值转换为具有定义值的intfalse 转换为0true 转换为1。因此,下面的结构是完全合法的(假设ab 都只是bool 变量):

if (a & b) // OK, integral promotions take place (bool ---> int)

假设编译器总是将bool 值与true(例如0x1)和false0x0)的内部表示形式相同,则条件可以直接转换为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 为啥将函数包含在依赖项列表中被认为是一种性能优化?

用户登陆检验----没有优化,大神可以帮忙优化优化

为啥 mysql 优化器没有使用完整的索引?

Adam 优化器真的是 RMSprop 加动量吗?如果是,为啥它没有动量参数?

为啥 GCC 没有尽可能地优化这组分支和条件?

Oracle数据库运维方案及优化