并冲洗缓冲区

C ++入门书中,第(1)章提到了以下内容:

endl是一个特殊的值,称为操纵器,当写入到输出stream时,会有一个新行写入输出,并刷新与该设备关联的缓冲区通过冲洗缓冲区,我们确保用户将立即看到写入stream的输出。

这里“冲洗缓冲区”是什么意思?

输出在写入目标器件之前通常会被缓冲。 这样,在写入速度慢以访问设备(如文件)时,每个字符后都不必访问设备。

冲洗意味着清空缓冲区并将其写入设备。

C ++的iostream被缓冲,这意味着当你输出到一个ostream时,内容不会立即转到stream后面,例如cout的情况下的标准输出。 stream的实现决定了何时实际发送stream的缓冲部分。 这样做是出于效率的原因,将字节写入networking或磁盘stream是非常低效的,通过缓冲这个问题得到解决。

但是,这意味着当你写debugging消息到一个日志文件并且你的程序崩溃的时候,你可能会失去通过这个stream写入日志文件的部分数据,因为日志的一部分可能仍然在stream的缓冲区中,尚未写入实际文件。 为防止发生这种情况,您需要通过显式刷新方法调用或使用endl的方便来使stream清空其缓冲区。

但是,如果您只是定期写入文件,则应该使用\ n来代替endl,以防止stream不必要地冲洗每一行中的stream,从而降低性能。

编辑包括这个笔记:

cin和cout有特殊的关系,从cin中读取会自动刷新cout。 这样可以确保在从cin的读取等待input之前,例如你写给cout的提示符实际上会被用户看到。 因此,即使在cout中,您通常不需要endl,但可以使用\ n。 你可以在其他stream之间build立这样的关系,也可以把它们绑在一起。

这里“冲洗缓冲区”是什么意思?

std::endl会导致stream的内部暂存内存(“缓冲区”)中的数据被“刷新”(传输)到操作系统。 后续行为取决于stream映射到的设备types,但通常情况下,刷新会显示数据已物理传输到相关设备。 然而,突然失去权力可能会打败这种幻想。

这种冲洗涉及到一些开销 (浪费时间),因此当执行速度是一个重要的问题时应该尽量减less。 尽量减less这种开销的总体影响是数据缓冲的根本目的,但是这个目标可以被过度的冲洗所击败。


背景信息

计算系统的I / O通常非常复杂,由多个抽象层组成。 每个这样的层可能引入一定量的开销。 数据缓冲是通过最小化在系统的两层之间执行的单个事务的数量来减less这种开销的一种方式。

  • CPU /内存系统级caching(caching) :对于非常高的活动,即使计算机的随机存取内存系统也可能成为瓶颈。 为了解决这个问题,CPU通过提供多层隐藏的高速caching(其各个caching被称为高速caching行)来虚拟化内存访问。 这些处理器cachingcachingalgorithm的内存写入(根据写入策略 ),以尽量减less对内存总线的冗余访问。

  • 应用程序级缓冲 :虽然并不总是必要的,但应用程序在将输出数据传递到I / O库之前分配内存块来累积输出数据并不罕见。 这提供了允许随机访问(如果需要)的根本好处,但是这样做的一个重要原因是它最小化了与进行库调用相关的开销 – 这可能比简单地写入存储器arrays。

  • I / O库缓冲 : C ++ IOstream库可selectpipe理每个打开stream的缓冲区。 此缓冲区特别用于限制系统对操作系统内核的调用次数,因为这样的调用往往具有一些不重要的开销。 这是使用std::endl时刷新的缓冲区。

  • 操作系统内核和设备驱动程序 :操作系统根据stream所连接的输出设备将数据路由到特定的设备驱动程序(或子系统)。 在这一点上,实际行为可能会有很大的不同,这取决于这种types的设备的性质和特点。 例如,当设备是硬盘时,设备驱动程序可能不会立即传输到设备,而是维护自己的缓冲区,以进一步减less冗余操作(因为磁盘也是最有效地写入块)。 为了显式刷新内核级别的缓冲区,可能需要fsync() on Linux调用系统级函数(如fsync() on Linux即使closures相关的stream,也不一定强制执行此类刷新。

    示例输出设备可能包括…

    • 本地机器上的terminal
    • 远程机器上的terminal(通过SSH或类似的)
    • 数据通过pipe道或套接字发送到另一个应用程序
    • 海量存储设备和相关文件系统的许多变体,其可以(再次)本地连接或通过networking分布
  • 硬件缓冲区 :特定硬件可能包含自己的内存缓冲区。 例如,硬盘驱动器通常包含磁盘缓冲区 ,以便(除其他之外)允许物理写入发生,而不需要系统的CPU参与整个过程。

在许多情况下,这些不同的缓冲层倾向于(在一定程度上)多余 – 因此基本上是过度的。 然而,如果其他层由于任何原因不能针对与每层相关的开销提供最佳缓冲,则每层的缓冲可以提供吞吐量的巨大增益。

长话短说, std::endl 针对由特定stream的C ++ IOstream库pipe理的缓冲区。 在调用std::endl ,数据将被移动到内核级别的pipe理,接下来的数据取决于很多因素。


如何避免std::endl的开销

  • 方法1:不要使用std::endl – 而是使用'\n'
  • 方法2:不要使用std::endl – 使用类似下面的版本,而不是 …

 inline std::ostream & endl( std::ostream & os ) { os.put( os.widen('\n') ); // http://en.cppreference.com/w/cpp/io/manip/endl if ( debug_mode ) os.flush(); // supply 'debug_mode' however you want return os; } 

在这个例子中,你提供了一个自定义的endl ,可以在不调用flush()的内部调用的情况下被调用(这是强制传输到操作系统的东西)。 启用flush(使用debug_modevariables)对于debugging场景来说非常有用,在这种情况下,当程序终止之前,您希望能够检查输出(例如磁盘文件),然后清理closures相关的stream(这将迫使最终冲洗缓冲区)。

当使用std::cout ,在输出操作符( << )之后使用的操作数存储在缓冲区中,不会显示在stdin (通常是terminal或命令提示符)上,直到遇到std::endlstd::cin ,导致缓冲区被刷新 ,从某种意义上讲,显示/输出缓冲区的内容到stdin

考虑这个程序:

 #include <iostream> #include <unistd.h> int main(void) { std::cout << "Hello, world"; sleep(2); std::cout << std::endl; return 0; } 

获得的输出将是:

2秒后

你好,世界