printf 如果您只输出常量字符串,则函数会很慢,因为printf 必须扫描每个字符以查找格式说明符 (%)。 puts 之类的函数对于长字符串明显更快,因为它们基本上可以将输入字符串 memcpy 输入到输出 I/O 缓冲区中。
如果输入字符串是没有格式说明符且以换行符结尾的常量字符串,许多现代编译器(GCC、Clang,可能还有其他编译器)都具有自动将printf 转换为puts 的优化。因此,例如,编译以下代码:
printf("line 1\n");
printf("line 2\n");
printf("line 3"); /* no newline */
导致以下程序集(Clang 703.0.31,cc test.c -O2 -S):
...
leaq L_str(%rip), %rdi
callq _puts
leaq L_str.3(%rip), %rdi
callq _puts
leaq L_.str.2(%rip), %rdi
xorl %eax, %eax
callq _printf
...
换句话说,puts("line 1"); puts("line 2"); printf("line 3");。
如果您的长 printf 字符串不以换行符结尾,那么您的性能可能会显着比您使用 printf 调用一堆换行符终止的字符串,仅仅是因为这种优化。为了演示,请考虑以下程序:
#include <stdio.h>
#define S "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
#define L S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S
/* L is a constant string of 4000 'a's */
int main() {
int i;
for(i=0; i<1000000; i++) {
#ifdef SPLIT
printf(L "\n");
printf(S);
#else
printf(L "\n" S);
#endif
}
}
如果未定义SPLIT(生成一个没有终止换行符的printf),则时间如下所示:
[08/11 11:47:23] /tmp$ cc test.c -O2 -o test
[08/11 11:47:28] /tmp$ time ./test > /dev/null
real 0m2.203s
user 0m2.151s
sys 0m0.033s
如果定义了SPLIT(产生两个printfs,一个带有终止换行符,另一个没有),时序如下所示:
[08/11 11:48:05] /tmp$ time ./test > /dev/null
real 0m0.470s
user 0m0.435s
sys 0m0.026s
所以你可以看到,在这种情况下,将printf 分成两部分实际上会产生 4 倍的加速。当然,这是一个极端情况,但它说明了printf 可以如何根据输入进行可变优化。 (请注意,使用 fwrite 甚至更快 - 0.197s - 所以如果你真的想要速度,你应该考虑使用它!)。
tl;dr:如果您只打印较大的常量字符串,请完全避免使用 printf,并使用更快的函数,例如 puts 或 fwrite。