繁体   English   中英

为什么 GCC 不优化对 printf 的调用?

[英]Why doesn't GCC optimize this call to printf?

#include <stdio.h>
int main(void) { 
    int i;
    scanf("%d", &i);
    if(i != 30) { return(0); } 
    printf("i is equal to %d\n", i);
}

结果字符串似乎总是“i is equal to 30”,因此,例如,为什么 GCC 不通过调用puts()write()来优化对 printf 的调用?

(刚刚检查了生成的程序集,使用gcc -O3 (版本 5.3.1),或者在Godbolt Compiler Explorer 上

首先,问题不在于if 如您所见, gcc看穿if并设法将30直接传递给printf

现在, gcc确实有一些逻辑来处理printf特殊情况(特别是,它确实优化了printf("something\\n")甚至printf("%s\\n", "something")puts("something") ),但它非常具体,没有更进一步; printf("Hello %s\\n", "world")保持原样。 更糟糕的是,上面没有尾随换行符的任何变体都保持不变,即使它们可以转换为fputs("something", stdout)

我想这归结为两个主要问题:

  • 上述两种情况是非常容易实现并且经常发生的模式,但对于其余情况,可能很少值得付出努力; 如果字符串是常量并且性能很重要,那么程序员可以很容易地处理它——实际上,如果printf的性能很关键,他不应该依赖这种优化,这可能会在格式稍有变化时就崩溃字符串。

    如果你问我,即使只是上面的puts优化已经“追求风格点”:除了人工测试用例之外,你不会真正获得真正的性能。

  • 当你开始走出%s\\n的领域时, printf是一个雷区,因为它对运行时环境有很强的依赖性; 特别是,许多printf说明符(不幸的是)受语言环境的影响,此外还有许多特定于实现的怪癖和说明符(并且gcc可以与来自 glibc、musl、mingw/msvcrt 的printf工作,... - 和在编译当你不能调用目标 C 运行时——想想你在交叉编译时)。

    我同意这个简单的%d案例可能是安全的,但我可以理解为什么他们可能决定避免过于聪明而只在这里执行最愚蠢和最安全的优化。


对于好奇的读者, 这里是实际实现优化的地方; 如您所见,该函数匹配有限数量的非常简单的情况(除了 GIMPLE,自从撰写了这篇概述它们的好文章以来,并没有太大变化)。 顺便说一句,源实际上解释了为什么他们不能为非换行符情况实现fputs变体(在编译阶段没有简单的方法来引用stdout全局)。

现代编译器非常聪明,但还不够聪明,无法使用逻辑预见输出。 在这种情况下,人类程序员优化这段代码很简单,但这项任务对机器来说太难了。 事实上,对于程序(例如gcc)来说,在不运行的情况下预测程序的输出是不可能的。 有关证明,请参阅停机问题

无论如何,您不会期望所有没有输入的程序都针对多个puts()语句进行优化,因此 GCC 不优化包含一个scanf()语句的这段代码是完全合理的。


然而,这并不意味着编译器不能或不应该被优化以生成更优化的执行文件。 虽然不可能预测所有程序的结果,但改进其中的许多程序是完全可能且有希望的。

不确定这是否是一个令人信服的答案,但我希望编译器不应该将printf("%d\\n", 10) case 优化为puts("10")

为什么? 因为这个案例可能比你想象的更复杂。 以下是我目前能想到的一些问题:

  1. 将二进制数转换为 ASCII 会增加字符串文字的大小,从而增加整体代码大小。 虽然这与小数无关,但如果是printf("some number: %d", 10000) ---- 5 位或更多(假设int是 32 位),增加的字符串大小将超过保存的大小整数,有些人可能认为这是一个缺点。 是的,通过转换,我保存了一条“推送到堆栈”指令,但是指令有多少字节以及将保存多少字节是特定于架构的。 编译器说它是否值得是很重要的。

  2. Padding ,如果在格式中使用,还可以增加扩展字符串文字的大小。 示例: printf("some number: %10d", 100)

  3. 有时,由于代码大小的原因,开发人员会在 printf 调用之间共享一个格式字符串:

     printf("%-8s: %4d\\n", "foo", 100); printf("%-8s: %4d\\n", "bar", 500); printf("%-8s: %4d\\n", "baz", 1000); printf("%-8s: %4d\\n", "something", 10000);

    将它们转换为不同的字符串文字可能会失去大小优势。

  4. 对于%f%e%g ,存在小数点“.”的问题。 是语言环境相关的。 因此编译器无法为您将其扩展为字符串常量。 虽然我们只讨论了%d但为了完整性,我在这里提到了这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM