In the C++ world, one often hears the statement “iostream
s are slow, you should use printf
instead”. Is this true?
It is possible that this story results from abuse of endl
. Beginner's C++ books often recommend endl
over '\n'
for writing a newline. The observable result is often the same, but endl
results in extra overhead: it flushes the stream. This results in a system call, slowing things down while often unnecessary.
Using my newly written TimedSection
class, I tested whether the story is actually true, or just a myth. I tried three methods of printing:
cout
withendl
for line endingscout
with'\n'
for line endingsprintf
(also with'\n'
for line endings, so no forced flushing)
Each of these was tested in the following scenarios:
- printing an empty line
- printing a line containing a string literal
- printing a line containing a string variable, a string literal, and an integer variable
All strings used were 20 characters long; I figured this would be a typical average length for printing messages. Newline characters were absorbed into string constants wherever possible.
Each of these was run 10 million times, redirecting the output to /dev/null
. The testing machine is an Intel i7 920, running Ubuntu 10.4, Linux 2.6.32 and gcc 4.4.3.
Here are the results:
cout with only endl 1461.310252 ms cout with only '\n' 343.080217 ms printf with only '\n' 90.295948 ms cout with string constant and endl 1892.975381 ms cout with string constant and '\n' 416.123446 ms printf with string constant and '\n' 472.073070 ms cout with some stuff and endl 3496.489748 ms cout with some stuff and '\n' 2638.272046 ms printf with some stuff and '\n' 2520.318314 ms
Surprise! Yes, for printing newlines, printf
greatly outperforms cout
. We can also see the huge slowdown of using endl
inappropriately, something which is clearly to be avoided.
However, even for printing a moderately-sized string constant, cout
outperforms printf
. This effect becomes more pronounced as the string gets longer, probably because printf
has to do more processing per character.
Finally, for some slightly more complicated formatting, the difference is quite small. For longer string constants, it becomes even smaller.
We can conclude that iostream
s are definitely not always slower than C-style printf
. It depends on the particular use, and as the formatting becomes more complicated, the difference drops to zero. Part of the myth may be ascribed to inappropriate use of endl
; maybe another part is due to unoptimized implementations of the standard library. Either way: myth busted.
Update: For the curious, here is the full source code. You may need to link with -lrt
to get clock_gettime
.
#include <stdio.h> #include <iostream> #include <ctime> class TimedSection { char const *d_name; timespec d_start; public: TimedSection(char const *name) : d_name(name) { clock_gettime(CLOCK_REALTIME, &d_start); } ~TimedSection() { timespec end; clock_gettime(CLOCK_REALTIME, &end); double duration = 1e3 * (end.tv_sec - d_start.tv_sec) + 1e-6 * (end.tv_nsec - d_start.tv_nsec); std::cerr << d_name << '\t' << std::fixed << duration << " ms\n"; } }; int main() { const int iters = 10000000; char const *text = "01234567890123456789"; { TimedSection s("cout with only endl"); for (int i = 0; i < iters; ++i) std::cout << std::endl; } { TimedSection s("cout with only '\\n'"); for (int i = 0; i < iters; ++i) std::cout << '\n'; } { TimedSection s("printf with only '\\n'"); for (int i = 0; i < iters; ++i) printf("\n"); } { TimedSection s("cout with string constant and endl"); for (int i = 0; i < iters; ++i) std::cout << "01234567890123456789" << std::endl; } { TimedSection s("cout with string constant and '\\n'"); for (int i = 0; i < iters; ++i) std::cout << "01234567890123456789\n"; } { TimedSection s("printf with string constant and '\\n'"); for (int i = 0; i < iters; ++i) printf("01234567890123456789\n"); } { TimedSection s("cout with some stuff and endl"); for (int i = 0; i < iters; ++i) std::cout << text << "01234567890123456789" << i << std::endl; } { TimedSection s("cout with some stuff and '\\n'"); for (int i = 0; i < iters; ++i) std::cout << text << "01234567890123456789" << i << '\n'; } { TimedSection s("printf with some stuff and '\\n'"); for (int i = 0; i < iters; ++i) printf("%s01234567890123456789%i\n", text, i); } }
12 comments:
"myth busted"? Maybe if you provide the source code for your tests. And specified compile flags you used ;)
Still, from your number i'd deduce that the best way to be sure that it's quite fast is using printf. In certain cases cout is a bit faster, but in other ones printf is way slower. Right?
@Anonymous: I updated with the full source code and compile flags.
@Mark IJbema: I assume you mean "... cout is way slower." However, those cases are pathological; who'd want to print ten million newlines? In common cases, however, the speed difference is so small that we are better off using cout, gaining type-safety and (often) readability. (I know, there are cases where printf is more readable…)
Of course, there is not much formatting going on.
{
TimedSection s("cout number formatting");
for (int i = 0; i < iters; ++i)
std::cout << std::setfill('0') << std::setw(2) << i << " "
<< std::setfill(' ') << std::setw(4) << i << "\n";
}
{
TimedSection s("printf number formatting");
for (int i = 0; i < iters; ++i)
printf("%02d %4d\n", i, i);
}
These give me a results 3.7 seconds and 3.1 seconds. (With g++ and -O2; with default optimization level the numbers were 4.1 and 3.0.)
In practice, though, I don't care. But while printf isn't type safe nor extensible, it sure is more compact..
What compiler options did you use to make the test? Optimizations matter for streams, probably more than for printf...
@Nicola Bonelli: Yes, compile flags matter. Ignoring the endl cases (which are pathologically slow), on my system...
-O0 makes the first printf blazing fast (WTF?!). For the other cases, cout is slightly slower, but the difference is negligible.
-O1 makes cout slightly faster than printf in all cases.
-O2 makes cout a little bit faster compared to -O1; printf remains about the same.
-O3 gives nearly the same results as -O2.
Streams are template, the code is compiled with the application, that explains why the optimization flags matter. The code of printf, instead, is a builtin function of the gcc comper unless you pass -fno-builtin option, in which case the glbc library function is used.
It would be nice if you could post also the performance of boost::format, as well as the performance of my c++0x print (available here: http://code.google.com/p/nicola-bonelli-repo/source/browse/trunk/codes.cpp0x/print.hpp) :-)
Must keep knowledge of Matt Wilson's Fast Format alive -
http://www.fastformat.org/performance.html
`printf` is effectively byte-code interpreter for formatted printing. (Your compiler could, in fact, convert the `printf` format string to code, at least when it is a literal constant, but it doesn't).
You basically compared a run-time-interpreted formatting language against with hand-written, statically compiled stream inserter and manipulator calls, both of which are based on completely different buffering I/O libraries.
Hope 2019 finds you smarter!
Hope 2023 finds you smarter!
Post a Comment