imm*_*tal 10 c optimization gcc
假设我有一个表达式,其中只有一部分是不太可能的,但另一个是统计中性的:
if (could_be || very_improbable) {
DoSomething();
}
Run Code Online (Sandbox Code Playgroud)
如果我将非常不可能的位放在unlikely()宏中,它会以任何方式帮助编译器吗?
if (could_be || unlikely(very_improbable)) {
DoSomething();
}
Run Code Online (Sandbox Code Playgroud)
注意:我不是在问马科斯是如何工作的 - 我理解这一点.这里的问题是关于GCC,如果我只暗示其中的一部分,它是否能够优化表达式.我也认识到它可能在很大程度上取决于所讨论的表达方式 - 我对那些有这些宏经验的人很有吸引力.
是的,这是合理的,编译器可以并且确实在正确的场景中利用它.
在您的实际示例中,如果could_be并且very_improbable实际上是整数变量,那么在谓词的子表达式上插入likely或unlikely宏将不会有任何意义,因为编译器可以做些什么才能使其更快?编译器可以if根据分支的可能结果来不同地组织块,但仅仅因为very_improbably不太可能没有帮助:它仍然需要生成代码来测试它.
让我们举个例子,编译器可以做更多工作:
extern int fn1();
extern int fn2();
extern int f(int x);
int test_likely(int a, int b) {
if (likely(f(a)) && unlikely(f(b)))
return fn1();
return fn2();
}
Run Code Online (Sandbox Code Playgroud)
这里的谓词是由两个呼叫的到f()与参数,和icc3产生不同的代码出的4种组合的likely和unlikely:
代码产生了likely(f(a)) && likely(f(b)):
test_likely(int, int):
push r15 #8.31
mov r15d, esi #8.31
call f(int) #9.7
test eax, eax #9.7
je ..B1.7 # Prob 5% #9.7
mov edi, r15d #9.23
call f(int) #9.23
test eax, eax #9.23
je ..B1.7 # Prob 5% #9.23
pop r15 #10.12
jmp fn1() #10.12
..B1.7: # Preds ..B1.4 ..B1.2
pop r15 #11.10
jmp fn2() #11.10
Run Code Online (Sandbox Code Playgroud)
在这里,两个谓词都可能是正确的,因此icc在两者都为真的情况下产生直线代码,如果结果为假,则跳出线外.
代码产生了unlikely(f(a)) && likely(f(b)):
test_likely(int, int):
push r15 #8.31
mov r15d, esi #8.31
call f(int) #9.7
test eax, eax #9.7
jne ..B1.5 # Prob 5% #9.7
..B1.3: # Preds ..B1.6 ..B1.2
pop r15 #11.10
jmp fn2() #11.10
..B1.5: # Preds ..B1.2
mov edi, r15d #9.25
call f(int) #9.25
test eax, eax #9.25
je ..B1.3 # Prob 5% #9.25
pop r15 #10.12
jmp fn1() #10.12
Run Code Online (Sandbox Code Playgroud)
现在,谓词很可能是假的,因此icc产生直线代码,在这种情况下直接导致返回,并跳出行B1.5以继续谓词.在这种情况下,预计第二呼叫(f(b))是真实的,因此它通过在结束码生成下降尾部调用到fn1().如果第二个调用结果为false,则它会跳回到已经为第一个跳转(标签B1.3)中的掉落情况组装的相同序列.
这也是生成的代码unlikely(f(a)) && unlikely(f(b)).在这种情况下,您可以想象编译器更改代码的结尾以将a jmp fn2()作为连接案例,但事实并非如此.值得注意的是,这会阻止重复使用前面的序列,B1.3并且我们甚至不太可能执行此代码,因此,优化较小的代码大小优于已经不太可能的情况似乎是合理的.
代码产生了likely(f(a)) && unlikely(f(b)):
test_likely(int, int):
push r15 #8.31
mov r15d, esi #8.31
call f(int) #9.7
test eax, eax #9.7
je ..B1.5 # Prob 5% #9.7
mov edi, r15d #9.23
call f(int) #9.23
test eax, eax #9.23
jne ..B1.7 # Prob 5% #9.23
..B1.5: # Preds ..B1.4 ..B1.2
pop r15 #11.10
jmp fn2() #11.10
..B1.7: # Preds ..B1.4
pop r15 #10.12
jmp fn1() #10.12
Run Code Online (Sandbox Code Playgroud)
这类似于第一个case(likely && likely),除了第二个谓词的期望现在是假的,因此它重新排序块,以便return fn2()情况是直通的.
因此编译器肯定可以使用精确likely和unlikely信息,实际上它是有道理的:如果你将上面的测试分解为两个链式if语句,很明显单独的分支提示可以工作,所以在语义上等效使用&&仍然不足为奇提示的好处.
这里有一些其他注意事项没有得到"全文"处理,以防你这么做:
icc举例说明了这些例子,但是对于这个测试至少两个clang并gcc进行相同的基本优化(以不同方式编译4个案例中的3个).likely(X) && unlikely(Y),你可以先检查条件Y,因为它很可能允许你快捷检查Y 1.显然,gcc可以对简单谓词进行优化,但是我无法哄骗icc或clang这样做.gcc优化显然非常脆弱:如果稍微更改谓词,它就会消失,即使在这种情况下优化会更好.1.当然,这只是允许当编译器可以看到,X并Y没有任何副作用,它可能不是有效的,如果Y是贵得多相比,检查X(因为任何避免检查的好处Y是由高不堪重负额外X评估的费用).