GCC为什么一直没有运行优化?
我用 C 编写了众所周知的交换函数,并使用 gcc S 观看了汇编输出,并再次做了同样的事情,但对 O2 进行了优化
差异非常大,因为与 20 行相比,我只看到了 5 行。
我的问题是,如果优化真的有帮助,为什么不一直使用它?为什么我们非优化编译代码?
给业内人士一个额外的问题,当您在测试后发布程序的最终版本时,您是否进行了优化编译?
我正在回复您的所有评论,请阅读。
回答
有几个原因。
1.编译时间较长
对于小型甚至中型项目,这在今天很少成为问题。现代计算机速度非常快。如果需要五到十秒通常没有关系。但对于较大的项目,它确实很重要。特别是如果构建过程设置不正确。我记得当我试图向游戏The Battle for Wesnoth添加一个功能时。编译花了大约十分钟。如果可以的话,很容易看出您希望将其减少到 5 分钟或更短的时间。
2.优化后的代码更难调试
它使代码更难调试的原因是调试器不会逐行运行程序。那只是一种错觉。这是一个可能存在问题的示例:
int main(void) {
char str[] = "Hello, World!";
int number_of_capital_letters = 0;
for(int i=0; i<strlen(str); i++) {
if(isupper(str[i]))
number_of_capital_letters++;
}
printf("%sn", str);
// Outcommented for debugging reasons
// printf("%dn", number_of_capital_letters);
}
您启动调试器并想知道为什么它不跟踪number_of_capital_letters. 然后您发现,由于您已注释掉最后一条printf语句,因此该变量未用于任何可观察的行为,因此优化器将您的代码更改为:
int main(void) {
puts("Hello, World!");
}
有人可能会争辩说,您只需关闭调试版本的优化器即可。当牛是一个球体时,这在世界上也是如此。但第三个原因是
3. 有时错误只出现在更高的优化级别。
想象一下,您有一个庞大的代码库。当你升级编译器时,一个bug突然出现。当您删除优化时,它似乎消失了。这里有什么问题?好吧,这可能是优化器中的错误。但它也可能是您的代码中的一个错误,它在优化器的新版本中表现出来。通常,具有未定义行为的代码在经过优化编译的代码中表现不同。
所以你会怎么做?您可以尝试找出错误是在优化器中还是在您的代码中。这可能是一项非常耗时的任务。让我们假设这是优化器中的一个错误。该怎么办?您可以降级您的编译器,由于多种原因,这不是最佳选择。特别是如果它是一个开源项目。想象一下,下载源代码,然后运行构建脚本,花了几个小时摸索着弄清楚出了什么问题,然后您在一些文档中看到(如果作者记录了它),您需要特定版本的特定编译器。
让我们假设它是您代码中的一个错误。理想的事情当然是修复它。但也许您没有这样做的资源。这一次,您还可以要求编译它的任何人使用特定编译器的某个版本。
但是,如果您可以编辑 Makefile 并替换-O3为-O2,您可以清楚地看到,在时间不是无穷无尽的资源的非理想世界中,有时这是一个可行的选择。运气不好的话,这样的错误可能需要一周的时间才能找到。或者更多。那是你可以在其他地方度过的时间。
这是此类错误的示例:
#include <stdio.h>
int main(void) {
char str[] = "Hello";
str[5] = '!';
puts(str);
}
当我用 gcc 10.2 编译它时,根据优化级别,我得到了不同的结果。
无优化:
Hello!
有优化:
Hello!`@
自己试试:
https://godbolt.org/z/5dcKKrEW1
https://godbolt.org/z/48bz5ae1d
在这里,我找到了一个论坛线程,其中调试构建有效但未发布:https : //developer.apple.com/forums/thread/15112
4. 有时错误只出现在较低的优化级别。
是的,这也可能发生。在这种情况下,如果您不太关心正确性,则可以增加优化。但是,如果您确实在意,这可能是一种查找错误的方法。如果您的代码在优化和未优化的情况下都能正确运行,与仅进行优化编译相比,它更有可能不包含将来困扰您的错误。
我没有找到一个有效的例子,但理论上可能会这样做。
int main(void) {
if(1/0) // Division by zero
puts("An error has occurred");
else
puts("Everything is fine");
}
如果这是在没有优化的情况下编译的,它很可能会崩溃。但是优化器可能会假设未定义的行为(例如除以零)永远不会发生,因此它会将代码优化为:
int main(void) {
puts("Everything is fine");
}
假设这1/0是某种不太可能评估为真的错误检查,因此您通常会假设程序打印“一切都很好”。在这里,优化器隐藏了一个错误。
5. 优化器可能会生成一个更大的二进制文件,或者正在使用更多的内存。或者其他不受欢迎的东西。
这有时很重要。特别是在嵌入式系统中。通常(总是)-O0产生非常大的代码,但您可能想要使用-Os(优化大小而不是速度)而不是-O3获得一个小的二进制文件。有时也是为了获得更快的代码。见下文。
6. 优化器可能会产生较慢的代码
是的,真的。这并不经常,但可能会发生。此问题中说明了一个相关但不等效的示例,其中编译器在优化可执行文件的大小时生成比速度更快的代码。