if (could_be || very_improbable) { DoSomething(); }
如果我将非常不可能的位置放在不可能的()宏中,它会以任何方式帮助编译器吗?
if (could_be || unlikely(very_improbable)) { DoSomething(); }
注意:我不是问马尔科如何工作 – 我明白了.这里的问题是关于海湾合作委员会,如果我只提到其中的一部分,它将能够优化表达式.我也认识到这可能在很大程度上取决于有关的表达方式 – 我对那些有这些宏经验的人感兴趣.
解决方法
在实际的例子中,如果can_be和very_improbable实际上是一个整数变量,那么在谓词的子表达式上插入可能或不太可能的宏并不是什么意思,因为编译器真的可以做些什么来使这个更快?编译器可以根据分支的可能结果来组织if块,但是因为很可能不太可能不会有帮助:它仍然需要生成代码来测试它.
让我们举个例子,编译器可以做更多的工作:
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(); }
这里的谓词由两个带有参数的f()调用组成,而icc为可能和不太可能的4种组合中的3个产生不同的代码:
(f(a))&&&&可能(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
在这里,这两个谓词都可能是真实的,因此icc为两个都是真实的情况下生成直线代码,如果出现这种情况,则跳转到外线.
代码produced不可能(f(a))&&可能(F(B)):
test_likely(int,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
现在,谓词可能是错误的,所以icc在这种情况下产生直接导致返回的直线代码,并跳到B1.5继续谓词.在这种情况下,它希望第二个调用(f(b))为真,因此它会通过以tail-call到fn1()结束的代码生成下降.如果第二个调用结果为假,它将跳回到已经组装的相同序列,用于第一次跳转(标签B1.3)中的下降.
事实证明也是由于不可能产生的代码(f(a))&&不可能的(F(B)).在这种情况下,您可以想象编译器会将代码的结尾更改为将jmp fn2()作为落选情况,但不会.注意到这将阻止在B1.3中重新使用较早的序列,这也是我们甚至不太可能执行这个代码,所以似乎合理的是通过优化较小的代码大小来优化一个已经不太可能的情况.
(f(a))&&&&&不可能的(F(b))的:
test_likely(int,eax #9.7 je ..B1.5 # Prob 5% #9.7 mov edi,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
这与第一种情况类似(可能和可能),除了第二谓词的期望现在是假的,所以它重新排序块,使得返回fn2()的情况是落后的.
所以编译器绝对可以使用精确的可能和不太可能的信息,确实是有道理的:如果你将上面的测试分解成两个链接的if语句,很明显,单独的分支提示将工作,所以这并不奇怪,语义上相当于使用&&仍然受益于提示.
这里还有一些其他的说明,没有得到“全文”的处理,万一你有这样的事情:
>我使用icc来说明这些例子,但是对于这个测试,至少两个clang和gcc都进行相同的基本优化(以4种不同的方式编译3个).
>编译器通过了解子谓词的概率可以做出的一个“明显”优化是反转谓词的顺序.例如,如果你有可能(X)&&不太可能(Y),您可以首先检查条件Y,因为它很可能允许您快速检查Y1.显然,gcc可以为make this optimization的简单谓词,但是我无法哄骗icc或clang来做到这一点. gcc优化显然是非常脆弱的:如果您更改谓词有一点disappears,即使在这种情况下优化会更好.
>编译器在不能保证变换后的代码将按照语言语义直接编译时,就被限制进行优化.特别是,除了可以证明这些操作没有副作用之外,它们的操作重新排序的能力有限.在构建您的谓词时,请记住这一点.
1当然,只有当编译器看到X和Y没有副作用时,才允许这样做,如果Y比X更昂贵,则可能无效(因为避免检查Y的任何好处是被额外的X评估的高成本所压倒).