用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
虽然这在这种情况下非常有意义,但它提出了两个问题:
答案 0 :(得分:3)
- 优化编译中函数调用替换的频率是多少?这是常见问题还是stdio例外?
醇>
在LLVM中用printf
替换puts
的优化位于类LibCallSimplifier
中。您可以在llvm/include/llvm/Transforms/Utils/SimplifyLibCalls.h中看到头文件,在llvm/lib/Transforms/Utils/SimplifyLibCalls.cpp中看到实现。查看这些文件将给出一些已完成的其他库调用替换优化的示例(头文件可能更容易开始)。当然还有其他许多LLVM所做的优化,你可以通过查看LLVM passes列表来了解其中一些优化。
- 我可以为自己的库编写编译器优化吗?我该怎么做?
醇>
是的,你可以。 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()
执行此操作