-O2优化printf("%s \ n",str)到puts(str)

时间:2016-03-31 21:03:26

标签: c assembly optimization clang

clang四处寻找,我编译了一个包含这一行的C程序:

printf("%s\n", argv[0]);

在没有优化的情况下进行编译时,在设置寄存器后,程序集输出调用printf

movq    (%rcx), %rsi
movq    %rax, %rdi
movb    $0, %al
callq   _printf

我尝试使用clang -O2进行编译。 printf来电已更换为puts来电:

movq    (%rsi), %rdi
callq   _puts

虽然这在这种情况下非常有意义,但它提出了两个问题:

  1. 优化编译中函数调用替换的频率是多少?这是常见问题还是stdio例外?
  2. 我可以为自己的库编写编译器优化吗?我该怎么做?

3 个答案:

答案 0 :(得分:3)

  
      
  1. 优化编译中函数调用替换的频率是多少?这是常见问题还是stdio例外?
  2.   

在LLVM中用printf替换puts的优化位于类LibCallSimplifier中。您可以在llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h中看到头文件,在llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp中看到实现。查看这些文件将给出一些已完成的其他库调用替换优化的示例(头文件可能更容易开始)。当然还有其他许多LLVM所做的优化,你可以通过查看LLVM passes列表来了解其中一些优化。

  
      
  1. 我可以为自己的库编写编译器优化吗?我该怎么做?
  2.   

是的,你可以。 LLVM非常模块化,可以在一系列过程中对IR进行转换。因此,如果您想为自己的库添加一个自定义传递,您可以这样做(尽管理解LLVM编译器流如何工作仍然是一项相当大的工作)。一个很好的起点是文件:Writing an LLVM Pass

答案 1 :(得分:2)

这种优化取决于编译器知道名为printf的函数只能是C标准定义的printf函数。如果程序将printf定义为其他内容,则程序将调用未定义的行为。这使得编译器可以在调用标准puts函数的情况下将调用替换为printf。它并不担心它“工作”就像调用了用户定义的printf函数一样。因此,这些函数替换优化几乎仅限于C或C ++标准中定义的函数。 (如果编译器以某种方式知道给定的标准有效,也许还有其他标准。)

如果没有自己修改编译器的源代码,就无法告诉编译器这些函数替换是否可以使用您自己的函数。但是,由于存在限制,您可以使用内联函数执行类似操作。例如,您可以使用以下内容实现与printf / puts优化类似的内容:

inline int myprintf(char const *fmt, char const *arg) {
    if (strcmp(fmt, "%s\n") == 0) {
         return myputs(args);
    }
    return _myprintf_impl(fmt, arg)
}

启用优化后,编译器可以在编译时选择基于fmt参数调用哪个函数,但只有在它可以确定它是一个常量字符串时。如果它不能,或者没有启用优化,那么编译器必须发出代码,在每次调用时检查它,这很容易将其转化为悲观。请注意,此优化取决于编译器知道strcmp如何工作并完全删除调用,因此编译器可以进行另一个库函数调用替换的示例。

您可以使用GCC的__builtin_constant_p功能改进此功能:

inline int myprintf(char const *fmt, char const *arg) {
        if (__builtin_constant_p(fmt[0])
            && strcmp(fmt, "%s\n") == 0) {
                return myputs(arg);
        }
        return _myprintf_impl(fmt, arg);
}

在GCC下,这导致代码永远不会检查格式字符串的运行时间。如果可以在编译时确定fmt"%s\n",则它会生成无条件调用myputs的代码,否则会生成无条件调用_myprintf_impl的代码。因此,启用优化后,此功能永远不会是悲观。不幸的是,当clang支持__builtin_constant_p函数时,我的clang版本总是生成无条件调用_myprintf_impl的代码。

答案 2 :(得分:-4)

puts是一个比printf小得多的函数,可执行文件的大小通常只有一半。只有在将数字转换为字符串进行打印时才需要printf,您可以使用 itoa()

执行此操作