-O2将printf(“%s \ n”,str)优化为puts(str)
用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
虽然这在这种情况下非常有意义,但它提出了两个问题:
- 在优化编译中,函数调用替换的频率是多少? 这是频繁的还是stdio是个例外?
- 我可以为自己的库编写编译器优化吗? 我该怎么办?
- 在优化编译中,函数调用替换的频率是多少? 这是频繁的还是stdio是个例外?
使用LLVM替换printf
的优化是在LibCallSimplifier
类中。 您可以在llvm / include / llvm / Transforms / Utils / SimplifyLibCalls.h中查看头文件,并在llvm / lib / Transforms / Utils / SimplifyLibCalls.cpp中查看实现。 查看这些文件将给出一些已完成的其他库调用替换优化的示例(头文件可能更容易开始)。 当然还有LLVM所做的其他许多其他优化,你可以通过查看LLVM传递列表来了解其中的一些优化。
- 我可以为自己的库编写编译器优化吗? 我该怎么办?
是的,你可以。 LLVM非常模块化,可以在一系列过程中对IR进行转换。 因此,如果您想为自己的库添加一个自定义传递,您可以这样做(尽管理解LLVM编译器流如何工作仍然是一项相当大的工作)。 一个很好的起点是文档: 编写LLVM Pass 。
这种优化取决于编译器知道名为printf
只能是C标准定义的printf
函数。 如果程序将printf
定义为其他内容,则程序将调用未定义的行为。 这使得编译器可以在调用标准printf
函数的情况下替换调用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
代码。 因此,启用优化后,此function永远不会是悲观。 不幸的是,当clang支持__builtin_constant_p
函数时,我的clang版本总是生成无条件调用_myprintf_impl
代码。
puts是一个比printf小得多的函数,可执行文件的大小通常只有一半。 只有在将数字转换为字符串进行打印时才需要printf,您可以使用itoa()执行此操作