Hello, and welcome to episode 7 of C++ Weekly. I'm your host Jason Turner. Once a week I'll pick some
topic of interest in C++ and dig into it with some live coding. In this episode I'm going to explain
to you and show you why you should stop using std end line. I often still see example code like
this: (this is a simple "hello world") we will say "hello world std endline"; from well-meaning
people and people who know what they're doing and this works just fine. We get "hello world." Now
the problem is an understanding of what exactly std::endl does. It turns out, and I only learned
this a few years ago myself, that std::endl is actually the equivalent of doing a newline
character followed by a flush. You get exactly the same results here. The problem is, do you
really need to be doing a flush? I'm guessing you don't, so I want to illustrate this with a quick
test of so performance characteristics. We're going to create a new file - "ofstream outfile,
output.txt," and we want to do ios_base trunc. We want it to be starting with a new file each
time we run this test just so that we don't have a file that's growing out of control. Let's write a
quick function that simply writes a line to an i/o string, so "write line, const, std string"
and we want it to be going to an ostream reference. All right, st, no, ostream, str,
std end line. We'll start with the end line example first, so let's say we want to write,
I don't know a hundred thousand lines out. We need something that's going to
be big enough that it's going to give us some performance characteristics to
actually compare. "Write line, hello world to outfile." That should compile - no it didn't.
Oh, forgot my name space qualifier.
Ostream... there we go. Alright, so that
wrote what we would expect to (expect it to) to this 'output.txt'. It's still
not writing quite enough data for us to actually get any kind of meaningful timing
measurement out of it. Okay, we're going to compile with optimizations this time.
So that takes approximately 1.71 seconds in the example
case where we are sending std end line. So let's instead do a newline character
and now it takes 0.1 seconds to do the exact same thing. That's clearly a significant performance
improvement. Now a little note here, this is something that I noticed in some example code
from Bjarne Stroustrup, is that really instead of sending a string we should be sending a single
character. This should be more efficient, although it's not something that you would ever really,
I think, should ever really come up timing-wise, but you are asking the stream operator to do
something very different. If you send it a string like this it's going to have to loop
over all the characters in the string looking for a null, and then stop when it's done;
but if you send it a single character it knows that it has exactly one thing to do - it needs
to write to that character to the output. So it's, interestingly, it's almost exactly the same. We
notice maybe slightly less memory usage because of the single character instead of the string
version. Let's see, that's 13K for the single character version for the compiled file size,
and it's actually a significantly smaller program if we send it the full string version,
but I still say you should prefer to just, if you're sending a character, send a character.
All right, so to get back to the point of this episode, we are looking at a difference of 1.7
seconds versus 0.1 seconds for end line versus not sending it end line. The problem is that
when you're sending something to the file system and you're sending 'flush', you're actually asking
the operating system to do something specific - you're asking it to flush all of the output
of the file into the actual file on disk. There's absolutely no reason to do that
in this case. Like, if you really, really cared to make sure that the file is flushed,
you could do it once at the very end for example. And yeah, we still get basically
the exact same timing. Where this comes up in code that I've seen is - I've been working on code that
generated very much output, so it would be like 'write_file_output' and it would take an ostream,
just like this; and then it would dump say, hundreds of lines that were like this:
What we ended up with was code where we ended up in this crazy situation that was unexpected and
took me personally awhile to figure out what's going, on where if you did the obvious thing,
and said 'outfile' and said 'write_file_output (outfile)' versus 'stringstream_writefile_output'
ss, and then outfile ss. Right, so this version with simply writing to the output file,
was something like 100 times slower, well at least 10 times slower, at least an order of magnitude
slower, than writing it to a stringstream first. This is just not the kind of behavior
that you would expect to have, because you're using presumably significantly more memory in
the stringstream version because you're having to construct the entire string of memory first;
and besides the fact that you're also increasing your compile time a whole lot by having to
instantiate all of these left stream operators, when instead you could have just incorporated
your newline into the string itself. So you get a smaller compile size, faster compile time,
faster run time unless you absolutely positively need to flush the string on every output. So
thanks for listening to this episode, and as always let me know if you have any comments
about my setup. You can find me on Twitter. You can find me on my website, emptycrate.com. Thanks.
you