多个printf()调用vs一个带长字符串的printf()调用?

时间:2016-08-11 12:59:05

标签: c string printf

假设我有一行printf()长字符串:

printf( "line 1\n"
"line 2\n"
"line 3\n"
"line 4\n"
"line 5\n"
"line 6\n"
"line 7\n"      
"line 8\n"
"line 9\n.. etc");  

与每行有多个printf()相比,此样式产生的费用是多少? 如果字符串太长,是否会出现堆栈溢出?

5 个答案:

答案 0 :(得分:16)

  

与每行多个printf()相比,此样式产生的成本是多少?

多个printf会导致多个函数调用,这是唯一的开销。

  

如果字符串太长,是否会出现堆栈溢出?

在这种情况下没有堆栈溢出。字符串文字通常存储在只读存储器中,而不是存储在堆栈存储器中。当字符串传递给printf时,只有指向其第一个元素的指针被复制到堆栈中。

编译器将处理此多行字符串"第1行\ n"

"line 2\n"
"line 3\n"
"line 4\n"
"line 5\n"
"line 6\n"
"line 7\n"      
"line 8\n"
"line 9\n.. etc"  

作为单个字符串

"line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\n.. etc"  

,这将存储在内存的只读部分。

但请注意(pmg中的comment)C11标准部分指出 5.2.4.1翻译限制表示

  

实施应能够翻译和执行至少一个程序,该程序至少包含以下每个限制的一个实例 18)
  [...]
   - 字符串文字中的4095个字符(连接后)
  [...]

答案 1 :(得分:10)

如果字符串文字由空格或空格分隔,则它会连接它们。所以下面

printf( "line 1\n"
"line 2\n"
"line 3\n"
"line 4\n"
"line 5\n"
"line 6\n"
"line 7\n"      
"line 8\n"
"line 9\n.. etc"); 

非常好,从可读性的角度来看很突出。此外,单个printf呼叫无疑可以比9 printf次呼叫的开销更小。

答案 2 :(得分:8)

如果您只输出常量字符串,则

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字符串以换行符结尾,那么您的表现可能显着比制作一堆{{1>用newline终止的字符串调用,只是因为这种优化。为了演示,请考虑以下程序:

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,一个带有终止换行,另一个没有),时间如下:

printf

所以你可以看到,在这种情况下,将[08/11 11:48:05] /tmp$ time ./test > /dev/null real 0m0.470s user 0m0.435s sys 0m0.026s 分成两部分实际上会产生4倍的加速。当然,这是一种极端情况,但它说明了printf如何根据输入进行可变的优化。 (请注意,使用printf甚至更快 - 0.197s - 所以如果你真的想要速度,你应该考虑使用它!)。

tl; dr:如果您只打印大的常量字符串,请完全避免fwrite并使用更快的函数,例如printfputs

答案 3 :(得分:3)

没有格式修饰符的printf被静默替换(也称为优化)到puts调用。这已经是加速了。在多次致电printf / puts时,您真的不想失去它。

GCC将printf(以及其他)作为内置函数,因此它可以在编译期间优化调用。

请参阅:

答案 4 :(得分:1)

每个额外的printf(或者如果你的编译器以这种方式优化它们)会每次都产生系统特定的函数调用开销,尽管优化无论如何都很有可能组合它们。

我还没有看到一个printf实现是一个叶子函数,所以期望额外的函数调用开销,比如vfprintf和它的被调用者。

然后,您可能会为每次写入产生某种系统调用开销。由于printf使用缓冲的stdout,因此通常可以避免其中一些(非常昂贵的)上下文切换......除了上面的所有示例都以新行结束。您的大部分费用可能都在这里。

如果您真的担心主线程中的成本,请将此类内容移至单独的线程中。