我有这样的代码,我发现它有点难以阅读:
// 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
?
我想说不应该,因为如果任何函数有副作用,它们在逻辑上就不等价。
但是,以下内容是等效的,并且它的优点是允许您为测试函数提供描述性名称,使代码更加自文档化:
// code3
inline bool combinedOp1()
{
return expensiveOperation1() && otherOperation() && foo();
}
inline bool combinedOp2()
{
return expensiveOperation2() && bar() && baz();
}
然后调用如下:
if (combinedOp1() || combinedOp2())
{
// do something
}
也许吧,但为什么不让你的第二次检查合并第一个呢?
// code3
bool expr = expensiveOperation1() && otherOperation() && foo();
expr = expr || (expensiveOperation2() && bar() && baz());
if(expr){
// one of the conditions met
}
更好的是,扭转局面,使最便宜的检查首先出现在每个列表中,利用惰性评估完全跳过昂贵的操作。
好吧,如果条件有副作用,编译器通常不会对 && 和 || 重新排序。 一些非常聪明的编译器可能能够静态地验证它们的独立性,但这很少见。
如果可能,重新排序条件,让便宜的操作先出现,这样就可以短路昂贵的操作。
这里最常见的答案是用“不应该”和“也许”来回答问题!这不是一个明确的答案来吧!
如果您想知道您的编译器是否正在优化这一小段代码,请使用“显示程序集输出”标志来编译您的代码。在 GCC 上,该标志是“-S”。 然后查看输出程序集,它将准确地 100% 显示正在编译或未编译的内容。
然后,您可以将截取的第一个代码与“therefromhere”的代码片段进行比较,并快速尝试大量代码更改,直到找到编译器优化最好的代码(即最少周期)。
查看 asm 输出听起来复杂且可怕,但实际上只需要大约 5 分钟即可完成。我在这里做了一个例子:在 C 中交换值的最快方法是什么?
这个问题的答案当然取决于编译器。检查的最终方法是查看编译器为此函数生成的程序集。大多数(全部?)编译器都有办法做到这一点,例如
gcc
有 -S
选项。如果由于某种奇怪的原因,您的调试器不能向您显示函数的反汇编,或者有其他工具可以执行此操作。
很好的答案。
我只想补充一点,我不喜欢编译器如此积极地优化以重新排序我的代码。
我只是想让编译器按照它的指示去做。
如果它能智胜我,它也能智胜自己。
如果编译器知道 cond2 中的函数(expectiveOperation2()、bar() 和 baz())是纯函数(即没有副作用),则可以进行优化。如果它们是纯函数,确保编译器知道的最简单方法就是使它们成为内联函数。
即使您不知道,编译器也有可能知道,但这不太可能,因为昂贵的Operation2()可能做了相当多的工作。
FWIW,如果这些函数是纯函数,您可能应该重新排序它们,以便 bar() 和 baz() 在昂贵的Operation2() 之前运行(与 cond1 中的排序相同)。