在编写高带宽数据流时,如何最好地管理Linux的缓冲行为?

时间:2009-11-17 22:27:05

标签: c++ c linux caching streaming

我的问题是:我有一个在Linux下运行的C / C ++应用程序,这个应用程序接收一个恒定速率的高带宽(约27MB /秒)数据流,它需要流式传输到文件(或文件)。它运行的计算机是运行Linux的四核2GHz Xeon。文件系统是ext4,磁盘是固态E-SATA驱动器,为此目的应该足够快。

问题是Linux过于聪明的缓冲行为。具体来说,不是立即将数据写入磁盘,也不是在我调用write()之后不久,Linux会将“写入”的数据存储在RAM中,然后在稍后的某个时间(我怀疑当2GB的RAM开始变满时)它会突然尝试同时向磁盘写出几百兆的缓存数据。问题是这个缓存刷新很大,并且在很长一段时间内阻止了数据采集代码,导致一些当前的输入数据丢失。

我的问题是:是否有任何合理的方法来“调整”Linux的缓存行为,因此要么根本不缓存传出数据,要么它必须缓存,它一次只缓存较小的数量,从而平滑了驱动器的带宽使用并提高了代码的性能?

我知道O_DIRECT,并且会使用我必须的,但它确实对程序有一些行为限制(例如缓冲区必须对齐并且磁盘扇区大小的倍数等)我宁愿如果我能,请避免。

7 个答案:

答案 0 :(得分:7)

您可以将posix_fadvise()POSIX_FADV_DONTNEED建议一起使用(可能与对fdatasync()的调用相结合),使系统刷新数据并从缓存中逐出。

有关实际示例,请参阅this article

答案 1 :(得分:4)

如果您有延迟要求,OS缓存无法独立满足(默认IO调度程序通常针对带宽而非延迟进行优化),您可能必须管理自己的内存缓冲。您是否立即写出传入的数据?如果你是,我建议放弃该架构并使用类似环形缓冲区的东西,其中一个线程(或多路复用I / O处理程序)从缓冲区的一侧写入,同时将读取复制到另一侧。

在某种程度上,这将足以处理父级OS缓存刷新所需的延迟。或者不是,在这种情况下,您实际上是带宽有限的,在您获得更快的存储空间之前,没有任何软件调整可以帮助您。

答案 2 :(得分:1)

您可以调整/ proc / sys / vm中的页面缓存设置(具体请参见/ proc / sys / vm / dirty_ratio,/ proc / sys / vm / swappiness)以根据自己的喜好调整页面缓存。

答案 3 :(得分:1)

如果我们谈论的是std :: fstream(或任何C ++流对象)

您可以使用以下方式指定自己的缓冲区:

  

streambuf * ios :: rdbuf(streambuf * streambuffer);

通过定义自己的缓冲区,您可以自定义流的行为。

或者,您始终可以按预先设定的时间间隔手动冲洗缓冲区。

注意:有一个缓冲区有共鸣。它比直接写入磁盘(每10个字节)更快。没有理由以小于磁盘块大小的块写入磁盘。如果你写得过于频繁,那么磁盘控制器就会成为你的瓶颈。

但是我有一个问题,你在write proccess中使用相同的线程需要阻止读取过程 在写入数据时,没有理由为什么另一个线程无法继续从您的流中读取数据(您可能需要一些花哨的步法来确保它们正在读取/写入缓冲区的不同区域)。但是我没有看到任何真正的潜在问题,因为IO系统将会异常地执行其工作(可能会拖延您的写入线程(取决于您对IO系统的使用),而不是无用的应用程序)。

答案 4 :(得分:1)

我知道这个问题已经过时了,但我们知道一些事情,现在我们还不知道这个问题何时被问到。

部分问题是/ proc / sys / vm / dirty_ratio和/ proc / sys / vm / dirty_background_ratio的默认值不适合具有大量内存的新机器。当达到dirty_background_ratio时,Linux开始刷新,并在达到dirty_ratio时阻止所有I / O. 降低 dirty_background_ratio以更快地开始刷新,引发 dirty_ratio以便稍后开始阻塞I / O.在非常大的内存系统(32GB或更多)上,您甚至可能想要使用dirty_bytes和dirty_background_bytes,因为_ratio设置的最小增量为1%太粗糙。请阅读https://lonesysadmin.net/2013/12/22/better-linux-disk-caching-performance-vm-dirty_ratio/以获取更详细的说明。

此外,如果您知道您不再需要再次读取数据,请使用FADV_DONTNEED调用posix_fadvise以确保可以更快地重用缓存页面。这必须在linux将页面刷新到磁盘后完成,否则刷新会将页面移回活动列表(实际上否定了fadvise的影响)。

为了确保在Linux阻止调用write()的情况下仍然可以读取传入数据,请在与您正在阅读的线程不同的线程中写入文件。

答案 5 :(得分:0)

好吧,试试这个十磅锤子解决方案,这可能对查看i / o系统缓存是否有助于解决问题很有用:每100 MB左右,调用sync()。

答案 6 :(得分:0)

您可以使用多线程方法 - 让一个线程只读取数据包并将它们添加到fifo,另一个线程从fifo中删除数据包并将它们写入磁盘。这样,即使写入磁盘停止,程序也可以继续读取传入数据并将其缓冲在RAM中。