假设我有一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");
Run Code Online (Sandbox Code Playgroud)
与printf()每条线路的多个线路相比,这种风格产生的成本是多少?
如果字符串太长,是否会出现堆栈溢出?
hac*_*cks 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"
Run Code Online (Sandbox Code Playgroud)
作为单个字符串
"line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\n.. etc"
Run Code Online (Sandbox Code Playgroud)
这将存储在内存的只读部分.
但请注意(在评论中用pmg指出)C11标准部分5.2.4.1翻译限制说明
该实现应能够翻译和执行至少一个包含以下每个限制的至少一个实例的程序18):
[...]
- 字符串文字中的4095个字符(连接后)
[...]
sjs*_*sam 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");
Run Code Online (Sandbox Code Playgroud)
非常好,从可读性的角度来看是很突出的.此外,单个printf呼叫无可争议地比9个printf呼叫具有更少的开销.
printf如果只输出常量字符串,则是一个慢函数,因为printf必须扫描格式说明符(%)的每个字符.喜欢的功能puts是显著更快的长字符串,因为它们基本上可以只memcpy输入字符串到输出I/O缓冲区.
许多现代编译器(GCC,Clang,可能是其他编译器)都有一个优化,如果输入字符串是一个常量字符串,没有以换行符结尾的格式说明符,它会自动转换printf为puts.因此,例如,编译以下代码:
printf("line 1\n");
printf("line 2\n");
printf("line 3"); /* no newline */
Run Code Online (Sandbox Code Playgroud)
导致以下程序集(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
...
Run Code Online (Sandbox Code Playgroud)
换句话说,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
}
}
Run Code Online (Sandbox Code Playgroud)
如果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
Run Code Online (Sandbox Code Playgroud)
如果SPLIT定义了(生成两个printfs,一个带有终止换行符,另一个没有终止换行符),则时间如下所示:
[08/11 11:48:05] /tmp$ time ./test > /dev/null
real 0m0.470s
user 0m0.435s
sys 0m0.026s
Run Code Online (Sandbox Code Playgroud)
所以你可以看到,在这种情况下,printf分成两部分实际上产生了4倍的加速.当然,这是一种极端情况,但它说明了如何printf根据输入进行可变的优化.(请注意,使用fwrite速度更快 - 0.197秒 - 所以如果你真的想要速度,你应该考虑使用它!).
tl; dr:如果你只打印大的,恒定的字符串,printf完全避免使用像puts或的更快的函数fwrite.
不带格式修饰符的调用printf将被静默替换(也称为优化)为调用puts。这已经是加速了。您真的不想在多次调用printf/时丢失它。puts
GCC 有printf(除其他外)作为内置函数,因此它可以在编译时优化调用。
看:
| 归档时间: |
|
| 查看次数: |
3028 次 |
| 最近记录: |