I am trying to understand how the output stream buffer works. I didn't find anything that explains the whole process of writing on a standard output in context of std::cout
. From what I understand, std::cout
doesn't actually prints on the standard output, rather it writes the content to output stream buffer of the terminal emulator. When the buffer is full, the terminal emulator deletes the contents of the buffer and writes it to the terminal display(standard output). According to me, this is what happens when a flush
operation is performed by the terminal emulator.
In the case of std::endl
, an explicit request is made to the terminal emulator to flush the contents of the buffer right away which causes a performance drop. This drop is caused due to the time taken by our program to create a flush
request to the terminal emulator and waiting for the contents of the buffer to be printed on the terminal display before proceeding to the next line of code. Without std::endl
, std::cout
doesn't care about having the contents printed right away. It leaves the responsibility of printing to the terminal emulator(by writing the content into the output stream buffer of the terminal emulator). I have a few doubts:
(1) Is my understanding of how terminal emulators display text requested by std::cout
, accurate?
(2) Is the flush
request made to the OS or the terminal emulator?
(3) If we increase the output stream buffer size of the terminal emulator, will there be an increase in the performance of the program complemented by a noticeable delay in printing the contents on the display?
No, your understanding is not correct. "Terminal emulators" are not involved, at least not directly.
The output buffer lives within the stream object itself (std::cout
in this case). There are three possible ways this buffer is used, depending on the buffering policy of the stream:
Block buffered: Output is stored in the buffer first. When the buffer is full, it is "flushed", i.e. its contents are written to the underlying (OS specific) output channel and the buffer is emptied.
Line buffered: Works like block buffered, but the buffer is also flushed whenever a newline ('\n'
) is written to it.
Unbuffered: The output buffer is not used. All output is written immediately.
When you open a file, the stream starts out being block buffered. std::cerr
is unbuffered. std::cout
is line buffered if output goes to a terminal and block buffered otherwise.
std::flush
flushes the output buffer immediately (if there is any).
As for how data is actually written, the details depend on your operating system. On unix systems, there is a system call called write
(a "system call" is a request to the operating system to do something). System calls are typically slower than normal function calls; the output buffer is a performance optimization because you don't want to call write
for every single character of output. Collecting output internally until you have a bigger batch of text to write means fewer calls to write
, which means better overall performance.
As for your specific questions:
No, terminal emulators are not relevant.
The OS.
The buffer is in the stream object, not the terminal emulator. Increasing the buffer size stops giving you any performance benefits after some point. Your program will usually spend most of its time calculating results and doing things other than writing text to std::cout
.