提问人:acenturyandabit 提问时间:1/23/2023 最后编辑:cafce25acenturyandabit 更新时间:1/23/2023 访问量:52
编译器是否会优化调用一次的函数
Will compiler optimize away called-once functions
问:
我一直在读 Robert C Martin 的《Clean Code》,其中一条建议是使用更多但更小的函数;即代替
int main(){
// do one thing
// ... 10 lines of code
// do another thing
// ... another 10 lines of code
// one last thing
// ... another 10 lines
return 0;
}
你应该
int main(){
doOneThing();
doAnotherThing();
oneLastThing();
}
void doOneThing(){
... 10 lines of code
}
// ... you get the idea
但是,根据我对低级语言的理解,我知道当调用函数时,变量被推送到堆栈上,堆栈指针递增,等等,而对于连续代码,则不需要这样做。
另一方面,编译器优化可以做一些很酷的事情,比如内联类方法。假设只调用一次,编译器是否可以调用此代码,推断出代码可以展开到,并完全消除函数调用和相关的运行时开销?doOneThing
main()
答:
这个问题的先前版本是关于C++或C的。我相信它需要一种语言的上下文才能以有意义的方式讨论这个话题,所以我选择继续使用 C++。
C++并不是真正的“低级”。您编写的代码不是 CPU 的指令。在代码和运行时实际发生的情况之间,有一个最复杂的软件部分:编译器。当你打开优化时,你的编译器将分析你的代码,并尝试生成最有效的可执行文件,在运行时的行为就像你的代码在字面上一样(即没有优化)。
这是由所谓的“假设”规则支配的。调用函数不是可观察的行为。如果函数很小,编译器将对函数的调用内联。
另一方面,调用函数当然是有代价的,但这个代价相对较小。当函数非常小时,例如只有 1 或 2 行,并且(这很重要!)由于某种原因编译器无法内联调用时,您需要开始担心这种开销。例如,这可能是由于函数是虚拟的。
你问编译器是否可以优化它,所以我只选一个例子。Gcc with 将为以下代码生成以下输出:-O3
int foo() { return 42;}
int bar() { return 0;}
int moo() { return 1;}
int main() {
return foo() + bar() + moo();
}
输出:
foo():
mov eax, 42
ret
bar():
xor eax, eax
ret
moo():
mov eax, 1
ret
main:
mov eax, 43
ret
您可以看到 hat 没有被调用任何函数。编译器检查表达式并发现它始终等于 。无需调用任何函数即可返回 。main
foo() + bar() + moo()
43
43
这是一个愚蠢的例子,尽管对于一般情况,如果你确实想看看编译器做了什么,你需要做同样的事情:查看编译器的输出。
为此,您需要先编写代码。首先推测哪种代码效率更高或效率更低是没有用的。您首先需要编写代码。而且因为无论如何你都需要这样做,所以你可以编写干净、简单、人类可理解的代码。这是你的编译器最能理解的代码,也知道如何优化,因为这也是其他程序员写的。
评论