假设我有一行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( "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()相比,这种风格的成本是多少?如果字符串太长,是否会出现堆栈溢出?
yhqotfr81#
与每行有多个printf()相比,这种风格的成本是多少?多个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"
"line 1\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在评论中指出)C11标准章节 *5.2.4.1翻译限制 * 指出,实现应能够翻译和执行至少一个程序,该程序至少包含以下限制中的每一个的一个示例18):[...]
[...]
pobjuy322#
如果字符串之间没有空格或空格,则C将它们连接起来。
字符串从可读性的Angular 来看,一个printf调用无疑比9个printf调用的开销要小。
frebpwbc3#
如果你只输出常量字符串,printf是一个很慢的函数,因为printf必须扫描每个字符的格式说明符(%)。像puts这样的函数对于长字符串来说要快得多,因为它们基本上只需要将输入字符串memcpy输入到输出I/O缓冲区。许多现代编译器(GCC,Clang,可能还有其他)都有一个优化,如果输入字符串是一个常量字符串,没有以换行符结尾的格式说明符,则会自动将printf转换为puts。例如,编译以下代码:
%
puts
memcpy
printf("line 1\n");printf("line 2\n");printf("line 3"); /* no newline */
printf("line 1\n");
printf("line 2\n");
printf("line 3"); /* no newline */
字符串生成以下程序集(Clang 703.0.31,cc test.c -O2 -S):
cc test.c -O2 -S
...leaq L_str(%rip), %rdicallq _putsleaq L_str.3(%rip), %rdicallq _putsleaq L_.str.2(%rip), %rdixorl %eax, %eaxcallq _printf...
...
leaq L_str(%rip), %rdi
callq _puts
leaq L_str.3(%rip), %rdi
leaq L_.str.2(%rip), %rdi
xorl %eax, %eax
callq _printf
型也就是puts("line 1"); puts("line 2"); printf("line 3");如果你的长printf字符串不以换行符结尾,那么你的性能可能会比你用换行符结尾的字符串进行一堆printf调用时差得多,仅仅是因为这种优化。为了演示,考虑下面的程序:
puts("line 1"); puts("line 2"); printf("line 3");
#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 }}
#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),时间如下所示:
SPLIT
[08/11 11:47:23] /tmp$ cc test.c -O2 -o test [08/11 11:47:28] /tmp$ time ./test > /dev/nullreal 0m2.203suser 0m2.151ssys 0m0.033s
[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/nullreal 0m0.470suser 0m0.435ssys 0m0.026s
[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。
fwrite
ca1c2owp4#
一个没有格式修饰符的printf被静默地替换(也就是优化)为一个puts调用。这已经是一个加速。你不想在多次调用printf/puts时失去它。GCC内置了printf(以及其他),因此它可以在编译时优化调用。参见:
li9yvcax5#
每一次额外的printf(或者如果你的编译器以这种方式优化它的话,则是puts)都会导致系统特定的函数调用开销,尽管优化很有可能会以任何方式将它们合并组合在一起。我还没有看到一个printf实现是一个叶函数,所以预计额外的函数调用开销像vfprintf和它的被调用者。那么每次写操作你可能会有一些系统调用开销。因为printf使用的是stdout,它是缓冲的,所以这些(真正昂贵的)上下文切换通常是可以避免的.除了上面所有的例子都以新行结束。你的大部分开销可能会在这里。如果你真的很担心主线程的开销,把这类东西移到一个单独的线程。
5条答案
按热度按时间yhqotfr81#
与每行有多个printf()相比,这种风格的成本是多少?
多个
printf
将导致多个函数调用,这是唯一的开销。如果字符串太长,是否会出现堆栈溢出?
在这种情况下没有堆栈溢出。字符串通常存储在只读内存中,而不是堆栈内存中。当字符串传递给
printf
时,只有指向其第一个元素的指针被复制到堆栈。加密器将处理此多行字符串
字符串
作为单字符串
型
并且这将被存储在存储器的只读部分中。
但请注意(由pmg在评论中指出)C11标准章节 *5.2.4.1翻译限制 * 指出,
实现应能够翻译和执行至少一个程序,该程序至少包含以下限制中的每一个的一个示例18):
[...]
[...]
pobjuy322#
如果字符串之间没有空格或空格,则C将它们连接起来。
字符串
从可读性的Angular 来看,一个
printf
调用无疑比9个printf
调用的开销要小。frebpwbc3#
如果你只输出常量字符串,
printf
是一个很慢的函数,因为printf
必须扫描每个字符的格式说明符(%
)。像puts
这样的函数对于长字符串来说要快得多,因为它们基本上只需要将输入字符串memcpy
输入到输出I/O缓冲区。许多现代编译器(GCC,Clang,可能还有其他)都有一个优化,如果输入字符串是一个常量字符串,没有以换行符结尾的格式说明符,则会自动将
printf
转换为puts
。例如,编译以下代码:字符串
生成以下程序集(Clang 703.0.31,
cc test.c -O2 -S
):型
也就是
puts("line 1"); puts("line 2"); printf("line 3");
如果你的长
printf
字符串不以换行符结尾,那么你的性能可能会比你用换行符结尾的字符串进行一堆printf
调用时差得多,仅仅是因为这种优化。为了演示,考虑下面的程序:型
如果没有定义
SPLIT
(生成一个没有终止换行符的printf
),时间如下所示:型
如果定义了
SPLIT
(产生两个printf
,一个有终止换行符,另一个没有),时间看起来像这样:型
因此,您可以看到,在本例中,将
printf
拆分为两部分实际上会产生4倍的加速。当然,这是一个极端的情况,但它说明了printf
如何根据输入进行优化。(请注意,使用fwrite
甚至更快- 0.197s -所以如果您真的想要速度,您应该考虑使用它!)。tl;dr:如果你只打印大的常量字符串,完全避免使用
printf
,使用更快的函数,比如puts
或fwrite
。ca1c2owp4#
一个没有格式修饰符的
printf
被静默地替换(也就是优化)为一个puts
调用。这已经是一个加速。你不想在多次调用printf
/puts
时失去它。GCC内置了
printf
(以及其他),因此它可以在编译时优化调用。参见:
li9yvcax5#
每一次额外的printf(或者如果你的编译器以这种方式优化它的话,则是puts)都会导致系统特定的函数调用开销,尽管优化很有可能会以任何方式将它们合并组合在一起。
我还没有看到一个printf实现是一个叶函数,所以预计额外的函数调用开销像vfprintf和它的被调用者。
那么每次写操作你可能会有一些系统调用开销。因为printf使用的是stdout,它是缓冲的,所以这些(真正昂贵的)上下文切换通常是可以避免的.除了上面所有的例子都以新行结束。你的大部分开销可能会在这里。
如果你真的很担心主线程的开销,把这类东西移到一个单独的线程。