eXPerience
eXPerience

Reputation: 279

Multiple printf() calls vs one printf() call with a long string?

Let's say I have one line of printf() with a long string:

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");  

What are the costs incurred by this style compared to having multiple printf()'s for each line?
Would there be a possible stack overflow if the string is too long?

Upvotes: 21

Views: 6461

Answers (5)

haccks
haccks

Reputation: 106122

what are the costs incurred by this style compared to having multiple printf()'s for each line ?

Multiple printf will result in multiple function calls and that's the only overhead.

Would there be a possible stack overflow if the string is too long?

No stack overflow in this case. String literals are generally stored in read only memory, not in stack memory. When a string is passed to printf then only a pointer to its first element is copied to the stack.

Compiler will treat this multi line string

"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"  

as single string

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

and this will be stored in read only section of the memory.

But note that (pointed by pmg in a comment) C11 standard section 5.2.4.1 Translation limits says that

The implementation shall be able to translate and execute at least one program that contains at least one instance of every one of the following limits18):
[...]

  • 4095 characters in a string literal (after concatenation)
    [...]

Upvotes: 17

nneonneo
nneonneo

Reputation: 179717

printf is a slow function if you are only outputting constant strings, because printf has to scan each and every character for a format specifier (%). Functions like puts are significantly faster for long strings because they can basically just memcpy the input string into the output I/O buffer.

Many modern compilers (GCC, Clang, probably others) have an optimization that automatically converts printf into puts if the input string is a constant string with no format specifiers that ends with a newline. So, for example, compiling the following code:

printf("line 1\n");
printf("line 2\n");
printf("line 3"); /* no newline */

results in the following assembly (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
...

in other words, puts("line 1"); puts("line 2"); printf("line 3");.

If your long printf string does not end with a newline, then your performance could be significantly worse than if you made a bunch of printf calls with newline-terminated strings, simply because of this optimization. To demonstrate, consider the following program:

#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
    }
}

If SPLIT is not defined (producing a single printf with no terminating newline), the timing looks like this:

[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

If SPLIT is defined (producing two printfs, one with a terminating newline, the other without), the timing looks like this:

[08/11 11:48:05] /tmp$ time ./test > /dev/null

real    0m0.470s
user    0m0.435s
sys 0m0.026s

So you can see, in this case splitting the printf into two parts actually produces a 4x speedup. Of course, this is an extreme case, but it illustrates how printf may be variably optimized depending on the input. (Note that using fwrite is even faster - 0.197s - so you should consider using that if you really want speed!).

tl;dr: if you are printing only large, constant strings, avoid printf entirely and use a faster function like puts or fwrite.

Upvotes: 10

technosaurus
technosaurus

Reputation: 7812

Each additional printf (or puts if your compiler optimizes it that way) will incur the system specific function call overhead each time, though there's a good probability that optimization will combine them anyhow.

I have yet to see a printf implementation that was a leaf function, so expect additional function call overheads for something like vfprintf and it's callees.

Then you'll likely have some sort of system call overheads for each write. Since printf uses stdout, which is buffered, some of these (really costly) context switches could normally be avoided... except all of the examples above end with new lines. Most of your cost will probably be here.

If you are really worried about cost in your main thread, move this kind of stuff to a separate thread.

Upvotes: 1

Koshinae
Koshinae

Reputation: 2330

A printf without format modifiers is silently replaced (aka. optimized) to a puts call. This is already a speedup. You don't really want to lose that on calling printf/puts multiple times.

GCC has printf (among others) as a builtin, so it can optimize the calls during compile time.

See:

Upvotes: 5

sjsam
sjsam

Reputation: 21965

C concatenates string literals if they are separated by nothing or by whitespace. So below

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"); 

is perfectly fine and stands out in the readability point of view. Also a single printf call unarguably has lesser overhead than 9 printf calls.

Upvotes: 11

Related Questions