endl并刷新缓冲区

Sim*_*ity 30 c++ buffer flush

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

endl是一个特殊的值,称为操纵器,当写入输出流时,具有向输出写入换行符并 释放与该设备关联的缓冲区的效果.通过浏览缓冲区,我们确保用户将立即看到写入流的输出.

在这里"冲洗缓冲区"是什么意思?

Ben*_*ley 35

输出通常在写入目标设备之前进行缓冲.这样,当写入慢速访问设备(如文件)时,它不必在每个单个字符后访问设备.

刷新意味着清空缓冲区并将其实际写入设备.


wic*_*ich 20

C++的iostream是缓冲的,这意味着当你输出到ostream时,内容不会立即转到流后面的内容,例如cout情况下的stdout.流的实现确定何时实际发送流的缓冲部分.这是出于效率的原因,逐字节写入网络或磁盘流是非常低效的,通过缓冲这个问题得以解决.

但这确实意味着当您将调试消息写入日志文件并且程序崩溃时,您可能会丢失通过流写入日志文件的部分数据,因为日志的一部分可能仍在流的缓冲区中尚未写入实际文件.要防止这种情况发生,您需要通过显式刷新方法调用或使用endl的便利性使流刷新其缓冲区.

但是,如果您只是定期写入文件,则应使用\n而不是endl来防止流在每行都不必要地刷新流,从而降低性能.

编辑包括此注释:

cin和cout有一种特殊的关系,从cin读取会事先自动冲洗cout.这样可以确保在从cin读取等待输入之前,用户实际上会看到您写入cout的提示.因此,即使在cout中你通常不需要endl但可以使用\n代替.您可以通过将它们绑在一起来在其他流之间创建这种关系.

  • @Daniel 当然重要的部分是“如果它进入终端”,您当然不应该假设 cout,因为它很可能会去其他任何地方。我的观点是,在写入 cout 时没有充分的理由使用 endl。但是,是的,我完全忘记了这一点。 (2认同)

nob*_*bar 7

在这里"冲洗缓冲区"是什么意思?

std::endl导致流的内部分段存储器(其"缓冲区")中的数据被"刷新"(传输)到操作系统.后续行为取决于流映射到的设备类型,但通常,刷新将提供数据已物理传输到关联设备的外观.然而,突然失去力量可能会打败这种错觉.

这种刷新涉及一些开销(浪费时间),因此当执行速度是一个重要问题时应该最小化.最小化这种开销的总体影响是数据缓冲的根本目的,但是这个目标可以通过过度刷新来消除.


背景资料

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

  • CPU /内存系统级缓冲(缓存):对于非常高的活动,即使计算机的随机存取存储器系统也可能成为瓶颈.为了解决这个问题,CPU通过提供多层隐藏缓存(其各个缓冲区称为缓存线)来虚拟化内存访问.这些处理器高速缓存缓冲算法的内存写入(根据写入策略),以最大限度地减少内存总线上的冗余访问.

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

  • I/O库缓冲:C++ IO流库可选择为每个打开的流管理缓冲区.特别地,该缓冲区用于限制对操作系统内核的系统调用的数量,因为这样的调用往往具有一些非平凡的开销. 这是使用时刷新的缓冲区std::endl.

  • 操作系统内核和设备驱动程序:操作系统根据流附加到哪个输出设备将数据路由到特定设备驱动程序(或子系统).此时,实际行为可能会有很大差异,具体取决于该类型设备的性质和特征.例如,当设备是硬盘时,设备驱动程序可能不会立即启动向设备的传输,而是维护自己的缓冲区以进一步减少冗余操作(因为磁盘也可以最有效地写入块中) ).为了显式刷新内核级缓冲区,可能需要调用系统级函数,例如fsync() on Linux- 即使关闭关联的流,也不一定强制执行此类刷新.

    示例输出设备可能包括......

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

在许多情况下,这些不同的缓冲层往往(在某种程度上)是多余的 - 因此基本上是过度的.然而,如果其他层由于某种原因未能提供关于与每层相关的开销的最佳缓冲,则每层的缓冲可以提供吞吐量的巨大增益.

简而言之,std::endl 解决了由特定流的C++ IO流库管理的缓冲区.调用之后std::endl,数据将被移动到内核级管理,接下来发生的数据取决于很多因素.


如何避免开销 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;
   }
Run Code Online (Sandbox Code Playgroud)

在此示例中,您提供了一个自定义endl,可以使用或不调用内部调用来调用flush()(这是强制传输到操作系统的因素).启用刷新(使用debug_mode变量)对于调试在程序终止之前能够检查输出(例如磁盘文件)的情况非常有用,然后才能彻底关闭关联的流(这会强制进行最终刷新缓冲区).