69 Intermediate The Hundred
The Newline That Cost a Syscall
two loops, the same 200 000 lines, one manipulator apart
using namespace std::chrono;
int main() {
auto t0 = steady_clock::now();
{
std::ofstream log("endl_demo.tmp");
for (int i = 0; i < 200000; ++i)
log << "event " << i << '\n';
}
auto t1 = steady_clock::now();
{
std::ofstream log("endl_demo.tmp");
for (int i = 0; i < 200000; ++i)
log << "event " << i << std::endl;
}
auto t2 = steady_clock::now();
std::cout << "'\\n': " << duration_cast<milliseconds>(t1 - t0).count() << " ms\n";
std::cout << "std::endl: " << duration_cast<milliseconds>(t2 - t1).count() << " ms\n";
std::remove("endl_demo.tmp");
}
Run it. How far apart are the two timings?
Answer
Not close: the flushing loop takes tens of times longer (GCC 11.5, x86-64 Linux, ext4):
One run of many. The ratio held above 10× on every run here, usually near 16×, -O0–-O3
alike — and understated: '\n' goes first, on a file it must create; swap the loops, 26×.
Why¶
std::endl writes exactly the byte '\n' writes — and then calls flush(). Flushing hands
the buffer to the kernel — nothing is fsynced — so the std::endl loop makes one write
syscall per line: strace -c counts 200 000. The '\n' loop fills the stream buffer (8 KiB
with libstdc++) and flushes only when full: 304 calls for the same 2.5 MB, 303 of them
writev — some 650× fewer crossings, and the crossing is the whole cost: on tmpfs, with no
disk in the path at all, std::endl still burns 300 ms. -Wall -Wextra says nothing.
The fix¶
Write the newline, and flush only when something is genuinely waiting to see the output:
log << "event " << i << '\n'; // buffered — the stream flushes when it is ready
std::cout << '.' << std::flush; // deliberate: a progress dot, wanted on screen now
A stream flushes when its buffer fills and again in its destructor, so only a crash loses the
tail — std::endl's one honest use. std::cerr has unitbuf set: it flushes every insertion.
Takeaway: std::endl is a newline plus a flush, and the flush is the expensive half.
Try it: g++ -std=c++17 main.cpp -o demo && ./demo