简体   繁体   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);
}

It appears that the resulting string will always be "i is equal to 30", so, why doesn't GCC optimize this call to printf with a call to puts() , or write() , for example?结果字符串似乎总是“i is equal to 30”,因此,例如,为什么 GCC 不通过调用puts()write()来优化对 printf 的调用?

(Just checked the generated assembly, with gcc -O3 (version 5.3.1), or on the Godbolt Compiler Explorer ) (刚刚检查了生成的程序集,使用gcc -O3 (版本 5.3.1),或者在Godbolt Compiler Explorer 上

First of all, the problem is not the if ;首先,问题不在于if as you saw, gcc sees through the if and manages to pass 30 straight to printf .如您所见, gcc看穿if并设法将30直接传递给printf

Now, gcc does have some logic to handle special cases of printf (in particular, it does optimize printf("something\\n") and even printf("%s\\n", "something") to puts("something") ), but it is extremely specific and doesn't go much further;现在, gcc确实有一些逻辑来处理printf特殊情况(特别是,它确实优化了printf("something\\n")甚至printf("%s\\n", "something")puts("something") ),但它非常具体,没有更进一步; printf("Hello %s\\n", "world") , for example, is left as-is. printf("Hello %s\\n", "world")保持原样。 Even worse, any of the variants above without a trailing newline are left untouched, even if they could be transformed to fputs("something", stdout) .更糟糕的是,上面没有尾随换行符的任何变体都保持不变,即使它们可以转换为fputs("something", stdout)

I imagine that this comes down to two main problems:我想这归结为两个主要问题:

  • the two cases above are extremely easy patterns to implement and happen quite frequently, but for the rest probably it's rarely worth the effort;上述两种情况是非常容易实现并且经常发生的模式,但对于其余情况,可能很少值得付出努力; if the string is constant and the performance is important, the programmer can take care of it easily - actually, if the performance of printf is critical he shouldn't be relying on this kind of optimization, which may break at the slightest change of format string.如果字符串是常量并且性能很重要,那么程序员可以很容易地处理它——实际上,如果printf的性能很关键,他不应该依赖这种优化,这可能会在格式稍有变化时就崩溃字符串。

    If you ask me, even just the puts optimizations above are already "going for the style points": you are not really going to gain serious performance in anything but artificial test cases.如果你问我,即使只是上面的puts优化已经“追求风格点”:除了人工测试用例之外,你不会真正获得真正的性能。

  • When you start to go outside the realm of %s\\n , printf is a minefield, because it has a strong dependency on the runtime environment;当你开始走出%s\\n的领域时, printf是一个雷区,因为它对运行时环境有很强的依赖性; in particular, many printf specifiers are (unfortunately) affected by the locale, plus there are a host of implementation-specific quirks and specifiers (and gcc can work with printf from glibc, musl, mingw/msvcrt, ... - and at compile time you cannot invoke the target C runtime - think when you are cross-compiling).特别是,许多printf说明符(不幸的是)受语言环境的影响,此外还有许多特定于实现的怪癖和说明符(并且gcc可以与来自 glibc、musl、mingw/msvcrt 的printf工作,... - 和在编译当你不能调用目标 C 运行时——想想你在交叉编译时)。

    I agree that this simple %d case is probably safe, but I can see why they probably decided to avoid being overly smart and only perform the dumbest and safest optimizations here.我同意这个简单的%d案例可能是安全的,但我可以理解为什么他们可能决定避免过于聪明而只在这里执行最愚蠢和最安全的优化。


For the curious reader, here is where this optimization is actually implemented;对于好奇的读者, 这里是实际实现优化的地方; as you can see, the function matches a restricted number of very simple cases (and GIMPLE aside, hasn't changed a lot since this nice article outlining them was written).如您所见,该函数匹配有限数量的非常简单的情况(除了 GIMPLE,自从撰写了这篇概述它们的好文章以来,并没有太大变化)。 Incidentally, the source actually explains why they couldn't implement the fputs variant for the non-newline case (there's no easy way to reference the stdout global at that compilation stage).顺便说一句,源实际上解释了为什么他们不能为非换行符情况实现fputs变体(在编译阶段没有简单的方法来引用stdout全局)。

Modern compilers are quite clever, but not clever enough to foresee the output using logic.现代编译器非常聪明,但还不够聪明,无法使用逻辑预见输出。 In this case, it's quite simple for human programmers to optimise this code, but this task is too hard for machines.在这种情况下,人类程序员优化这段代码很简单,但这项任务对机器来说太难了。 In fact, predicting the output of a program without running it is impossible for programs (gcc for example).事实上,对于程序(例如gcc)来说,在不运行的情况下预测程序的输出是不可能的。 For proof, see halting problem .有关证明,请参阅停机问题

Anyway, you don't expect all programs without inputs to be optimised to several puts() statements, so it's perfectly reasonable for GCC not to optimise this code containing one scanf() statement.无论如何,您不会期望所有没有输入的程序都针对多个puts()语句进行优化,因此 GCC 不优化包含一个scanf()语句的这段代码是完全合理的。


However, this does not mean compilers cannot or should not be optimised to generate more optimised executive files.然而,这并不意味着编译器不能或不应该被优化以生成更优化的执行文件。 Although it's impossible to predict the result all programs, it's perfectly possible and hopeful to improve many of them.虽然不可能预测所有程序的结果,但改进其中的许多程序是完全可能且有希望的。

Not sure if this is a convincing answer, but I would expect that compilers shouldn't optimize printf("%d\\n", 10) case to puts("10") .不确定这是否是一个令人信服的答案,但我希望编译器不应该将printf("%d\\n", 10) case 优化为puts("10")

Why?为什么? Because this case could be more complicated than you think.因为这个案例可能比你想象的更复杂。 Here are some of the problems I can think of at the moment:以下是我目前能想到的一些问题:

  1. Convert binary numbers to ASCII increases size of string literal , and thus overall code size.将二进制数转换为 ASCII 会增加字符串文字的大小,从而增加整体代码大小。 Although this is irrelevant to small numbers, but if it's printf("some number: %d", 10000) ---- 5 digits or more (assuming int is 32-bit), the string size increased will beat the size saved for the integer, and some people could consider this a drawback.虽然这与小数无关,但如果是printf("some number: %d", 10000) ---- 5 位或更多(假设int是 32 位),增加的字符串大小将超过保存的大小整数,有些人可能认为这是一个缺点。 Yes, with the conversion I saved a "push to stack" instruction, but how many bytes the instruction is and how many would be saved is architecture-specific.是的,通过转换,我保存了一条“推送到堆栈”指令,但是指令有多少字节以及将保存多少字节是特定于架构的。 It's non-trivial for a compiler to say if it's worth it.编译器说它是否值得是很重要的。

  2. Padding , if used in formats, can also increase size of expanded string literal. Padding ,如果在格式中使用,还可以增加扩展字符串文字的大小。 Example: printf("some number: %10d", 100)示例: printf("some number: %10d", 100)

  3. Sometimes I the developer would share a format string among printf calls, for code size reasons:有时,由于代码大小的原因,开发人员会在 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);

    Converting them to different string literals might lose the size advantage.将它们转换为不同的字符串文字可能会失去大小优势。

  4. For %f , %e , and %g , there is a problem that decimal point "."对于%f%e%g ,存在小数点“.”的问题。 is locale-dependent.是语言环境相关的。 Hence the compiler cannot expand it to string constant for you.因此编译器无法为您将其扩展为字符串常量。 Although we are only discussing about %d I mention this here for completeness.虽然我们只讨论了%d但为了完整性,我在这里提到了这一点。

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

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