我有这样的代码,我觉得它有点难以阅读:
// code1
if( (expensiveOperation1() && otherOperation() && foo())
|| (expensiveOperation2() && bar() && baz()) {
// do something
}
我刚将其更改为以下内容,以使其更具可读性:
// code2
const bool expr1 = expensiveOperation1() && otherOperation() && foo();
const bool expr2 = expensiveOperation2() && bar() && baz();
if(expr1 || expr2){
// one of the conditions met
}
但我现在应该关注效率吗?
我的意思是,在code1
中,如果第一个联合条款得到满足,那么它甚至不会费心去看第二个,因为已经很清楚该语句是真的。
但在我更具可读性的示例中,必须计算cond1
和cond2
。或者,如果在其他地方没有使用expr2,编译器是否足够聪明,可以将code2
更改为code1
?
答案 0 :(得分:24)
我会说它不应该,因为如果任何一个函数都有副作用,它们在逻辑上是不相等的。
但是,以下内容是等效的,并且它具有允许您为测试函数提供描述性名称的优势,使代码更加自我记录:
// code3
inline bool combinedOp1()
{
return expensiveOperation1() && otherOperation() && foo();
}
inline bool combinedOp2()
{
return expensiveOperation2() && bar() && baz();
}
然后按如下方式调用它:
if (combinedOp1() || combinedOp2())
{
// do something
}
答案 1 :(得分:20)
也许,但为什么不让你的第二张支票合并第一张?
// code3
bool expr = expensiveOperation1() && otherOperation() && foo();
expr = expr || (expensiveOperation2() && bar() && baz());
if(expr){
// one of the conditions met
}
更好的是,扭转局面,以便在每个列表中首先进行最便宜的检查,利用懒惰的评估来完全跳过昂贵的操作。
答案 2 :(得分:4)
嗯,一般情况下,编译器不会重新排序&&和s ||,因为条件有副作用。一些非常聪明的编译器可能能够静态地验证它们的独立性,但这种情况很少见。
如果可能的话,请先重新安排便宜操作的条件,这样就可以使昂贵的操作短路。
答案 3 :(得分:2)
这里的最佳答案是回答“不应该”和“可能”的问题!这不是一个确定的答案!
如果您想知道您的编译器是否正在优化这一小部分代码,请使用“show assembly output”标志编译代码。在GCC上,标志是“-S”。然后查看输出程序集,它将完全显示100%正在编译的内容。
然后,您可以将剪切的第一个代码与“wherehere”中的代码片段进行比较,并快速尝试大量代码更改,直到找到编译器优化最佳代码(即最小周期)。
查看asm输出听起来很复杂和可怕,但实际上只需要大约5分钟。我在这里做了一个例子:What is the fastest way to swap values in C?
答案 4 :(得分:1)
这个问题的答案当然取决于编译器。检查的最终方法是查看编译器为此函数生成的程序集。大多数(所有?)编译器都有办法执行此操作,例如gcc
具有-S
选项。如果由于某些奇怪的原因,您的大多数调试器都不会向您显示函数的反汇编,或者还有其他工具可以执行此操作。
答案 5 :(得分:0)
好的答案。
我只想补充一点,我不希望编译器在优化方面如此积极,以至于重新命令我的代码。
我只是想让编译器按照它所说的去做。
如果它能够超越我,它也可以超越自己。
答案 6 :(得分:0)
如果编译器知道cond2(expensiveOperation2(),bar()和baz())中的函数是纯的(即没有副作用),编译器可以进行优化。如果它们是纯粹的,那么确保编译器知道它的最简单方法就是使它们成为内联函数。
可能编译器可以告诉你,即使你没有,但是由于来自expensiveOperation2()可能做了很多工作,所以它的可能性很小。
FWIW,如果这些函数是纯函数,你应该重新排序它们,以便bar()和baz()在expensiveOperation2()之前运行(对于cond1中的排序也是如此)。