Mixing Cout and Printf For Faster Output

mixing cout and printf for faster output

The direct answer is that yes, that's okay.

A lot of people have thrown around various ideas of how to improve speed, but there seems to be quite a bit of disagreement over which is most effective. I decided to write a quick test program to get at least some idea of which techniques did what.

#include <iostream>
#include <string>
#include <sstream>
#include <time.h>
#include <iomanip>
#include <algorithm>
#include <iterator>
#include <stdio.h>

char fmt[] = "%s\n";
static const int count = 3000000;
static char const *const string = "This is a string.";
static std::string s = std::string(string) + "\n";

void show_time(void (*f)(), char const *caption) {
clock_t start = clock();
f();
clock_t ticks = clock()-start;
std::cerr << std::setw(30) << caption
<< ": "
<< (double)ticks/CLOCKS_PER_SEC << "\n";
}

void use_printf() {
for (int i=0; i<count; i++)
printf(fmt, string);
}

void use_puts() {
for (int i=0; i<count; i++)
puts(string);
}

void use_cout() {
for (int i=0; i<count; i++)
std::cout << string << "\n";
}

void use_cout_unsync() {
std::cout.sync_with_stdio(false);
for (int i=0; i<count; i++)
std::cout << string << "\n";
std::cout.sync_with_stdio(true);
}

void use_stringstream() {
std::stringstream temp;
for (int i=0; i<count; i++)
temp << string << "\n";
std::cout << temp.str();
}

void use_endl() {
for (int i=0; i<count; i++)
std::cout << string << std::endl;
}

void use_fill_n() {
std::fill_n(std::ostream_iterator<char const *>(std::cout, "\n"), count, string);
}

void use_write() {
for (int i = 0; i < count; i++)
std::cout.write(s.data(), s.size());
}

int main() {
show_time(use_printf, "Time using printf");
show_time(use_puts, "Time using puts");
show_time(use_cout, "Time using cout (synced)");
show_time(use_cout_unsync, "Time using cout (un-synced)");
show_time(use_stringstream, "Time using stringstream");
show_time(use_endl, "Time using endl");
show_time(use_fill_n, "Time using fill_n");
show_time(use_write, "Time using write");
return 0;
}

I ran this on Windows after compiling with VC++ 2013 (both x86 and x64 versions). Output from one run (with output redirected to a disk file) looked like this:

          Time using printf: 0.953
Time using puts: 0.567
Time using cout (synced): 0.736
Time using cout (un-synced): 0.714
Time using stringstream: 0.725
Time using endl: 20.097
Time using fill_n: 0.749
Time using write: 0.499

As expected, results vary, but there are a few points I found interesting:

  1. printf/puts are much faster than cout when writing to the NUL device
    • but cout keeps up quite nicely when writing to a real file
  2. Quite a few proposed optimizations accomplish little
    • In my testing, fill_n is about as fast as anything else
  3. By far the biggest optimization is avoiding endl
  4. cout.write gave the fastest time (though probably not by a significant margin

I've recently edited the code to force a call to printf. Anders Kaseorg was kind enough to point out--that g++ recognizes the specific sequence printf("%s\n", foo); is equivalent to puts(foo);, and generates code accordingly (i.e., generates code to call puts instead of printf). Moving the format string to a global array, and passing that as the format string produces identical output, but forces it to be produced via printf instead of puts. Of course, it's possible they might optimize around this some day as well, but at least for now (g++ 5.1) a test with g++ -O3 -S confirms that it's actually calling printf (where the previous code compiled to a call to puts).

which is faster, and which is more flexible: printf or cout?

Short Answer

Faster : printf

More flexible : cout

Long answer

When compared to the sprintf family, the C++ streams are supposed to be slower (by a factor 6 if I recall an item of Exceptional C++, by Herb Sutter). Still, most of the time, you won't need this speed, but you need to be sure your code won't be bugged.

And it is easy to do something wrong with the printf family of functions, be it putting the wrong number of arguments, the wrong types, or even introduce potential security vulnerability (the %n specifier comes to mind) in your code.

Unless really wanting it (and then, it's called sabotage), it's almost impossible to get it wrong with C++ streams. They handle seamlessly all known types (build-ins, std::strings, etc.), and it's easy to extend it. For example, let's say I have an object "Coordinate3D", and that I want to print out its data:

#include <iostream>

struct Coordinate3D
{
int x ;
int y ;
int z ;
} ;

std::ostream & operator << (std::ostream & p_stream
, const Coordinate3D & p_c)
{
return p_stream << "{ x : " << p_c.x
<< " , y : " << p_c.y
<< " , z : " << p_c.z << " }" ;
}

int main(int argc, char * argv[])
{
Coordinate3D A = {25,42,77} ;
std::cout << A << std::endl ;
// will print "{ x : 25 , y : 42 , z : 77 }"
return 0 ;
}

The problem with the stream is that they are quite difficult to handle correctly when wanting to specify format of some data (padding spaces for numbers, for example), and that sometimes, you really really need to go fast. Then, either fall back to printf, or try some high-speed C++ alternatives (FastFormat comes to mind).

Edit: Note that Thomas' series of tests show interesting results (which I reproduced right now on my computer), that is: cout and printf have similar performances when one avoids using std::endl (which flushes the output in addition to outputing a \n).

the buffer and output sequence of cout and printf

This is not a bug, nor is it anything to do with output buffering.

The order of execution of the i-- and i++ operations is not defined when they're invoked more than once as parameters to the same function call.

To elaborate (and possibly correct) Iraimbilanja's mention of "sequence points", the cout version is equivalent to:

(((cout << a) << b) << c)

Effectively, it's actually three separate function calls, each of whose parameters are evaluated in order, even though it's written like a single statement.

The << operator is really ostream& operator<<(ostream& os, int), so another way of writing this is:

operator<< ( operator<< ( operator<< ( cout, a ), b ), c )

Since for the outer call it's not (AFAIK) defined which order the two parameters are evaluated, it's perfectly possible for the right-hand "c" parameter (or in your case "i--") to happen before the left hand parameter is evaluated.

Switching to cout from printf - Complex format specifier patterns

Can cout even generate such a string in a simple manner with just one
line of code, or should I use sprintf to prepare my strings and then
feed them to cout?


I agree that sprintf() is not C++. It merely provides some manner of backward compatibility ... i.e. it has been provided specifically so that you can post-pone the conversion (of c to c++) and that technical debt to later in your schedule.


Here is a code sample from when I 'fixed' a log to be C++. (I left in the sprintf() to help document the new C++ code.)

  //retVal = ::sprintf(buff1, "%08llx  %2d:%02d:%02d,  %05llu.%03llu:  ",
// a_pid, hr, min, sec, (ms_of_day / 1000), (rt_ms % 1000));
// use stringstream formatting, not sprintf
buff1 << std::dec << std::setfill('0') << std::setw(8) << a_pid << " "
<< std::setfill('0') << std::setw(2) << hr << ":"
<< std::setfill('0') << std::setw(2) << min << ":"
<< std::setfill('0') << std::setw(2) << sec << ", "
<< std::setfill('0') << std::setw(5) << (ms_of_day / 1000)
<< "."
<< std::setfill('0') << std::setw(3) << (ms_of_day % 1000)
<< ": ";

I only had to do this once.

In a lot of ways, I do not miss the 'unsafe-type' style of sprintf.


If there is something special you do often, you might also consider creating something like the following.

std::string ssprintf0x08x(std::string label, void*  ptr)
{
std::stringstream ss;

ss << label << "0x"
<< std::hex << std::internal << std::setw(8)
<< std::setfill('0')
<< reinterpret_cast<uint64_t>(ptr);

return (ss.str());
}

I only had to implement this one time.


Answer to question:

Can cout even generate such a string in a simple manner with just one
line of code?

Yes. Of course.

C++ stream output has a learning curve, but it leads you to a type-safe approach for text output.

And, perhaps you are realizing, one line of code can be quite long.



Related Topics



Leave a reply



Submit