何时应该建立自己的I/O缓冲区系统(C++)?

6
我需要处理非常大的文本文件(2 GB),必须逐行读写。使用ofstream写入2300万行非常慢,因此一开始我尝试通过将大量行写入内存缓冲区(例如256 MB或512 MB),然后将缓冲区写入文件来加速该过程。但是这并没有起作用,性能差不多。我在读取文件时也有同样的问题。我知道STL I/O系统缓冲I/O操作,并且这也取决于磁盘调度程序策略(在我的情况下是Linux管理)。
你有什么关于如何提高性能的想法吗?
附注:我一直在考虑在程序处理数据时使用后台子进程(或线程)来读/写数据块,但我不知道(主要是在子进程的情况下)是否值得这样做。

请参考此答案,将文件直接读入stringstream缓冲区:https://dev59.com/xHVC5IYBdhLWcg3w-WOs - Martin York
你没有提到你看到的速度。根据物理驱动器,不要指望持续写入速度超过40-60 MB/s。使用dd来测试写入吞吐量。使用条带化驱动器(例如RAID 0、RAID 1+0)来增加吞吐量(最多可达~4个驱动器,然后总线将会受限)。 - Mitch Haile
7个回答

11

一个2GB的文件相当大,你需要考虑所有可能会成为瓶颈的因素:

  • 硬盘本身
  • 硬盘接口 (IDE/SATA/RAID/USB?)
  • 操作系统/文件系统
  • C/C++库
  • 你的代码

我建议首先进行一些测量:

  • 你的代码读写一个2GB文件需要多长时间,
  • 'dd' 命令读写磁盘的速度有多快?示例...

    dd if=/dev/zero bs=1024 count=2000000 of=file_2GB

  • 只使用大的fwrite()/fread()调用写入/读取需要多长时间

假设您的磁盘能够以大约40Mb/s的速度读/写(这可能是一个合理的起点),那么您的2GB文件最快也不会超过50秒。

实际上需要多长时间呢?

嗨Roddy,使用fstream读取方法和 大缓冲区(128,255或512 MB)的1.1 GB文件需要 大约43-48秒,使用fstream getline(line by line)也是一样。 cp需要将近2分钟来复制文件。

这种情况下,你的硬件已经达到了极限。cp需要读取和写入,当它这样做时将在磁盘表面上前后寻找,导致速度变慢。所以像你看到的那样,它会比简单的“读”情况至少慢两倍。

为了提高速度,我建议首先尝试更快的硬盘或SSD。

您没有说明磁盘接口是什么?SATA几乎是最简单/最快的选择。此外(显而易见的一点...),请确保磁盘物理上与运行代码的机器相同,否则您将受到网络限制...


1
如果你遇到了硬件限制,仅仅更换为速度稍微快一点的驱动器并不能像更换为条带驱动器那样有所帮助。另外,为什么要使用_cp_ 来进行测试呢?不如使用dd if=/dev/zero of=/path来测试写入吞吐量。尝试不同的块大小(bs=4K bs=32K),看看它们对速度的影响。 - Mitch Haile

8

5
也许你应该研究一下内存映射文件。 在这个库中检查它们:Boost.Interprocess

MMFs 也是我的建议。感谢提到 Boost 对其的支持。 - OregonGhost

3

仅供参考,避免使用std::endl,因为这会在缓冲区满之前强制刷新。取而代之的是使用'\n'来表示换行。


2
不要使用new来分配缓冲区,可以尝试使用std::vector<>。请注意保留HTML标签。
unsigned int      buffer_size = 64 * 1024 * 1024; // 64 MB for instance.
std::vector<char> data_buffer(buffer_size);
_file->read(&data_buffer[0], buffer_size);

请阅读关于使用下划线作为标识符命名的规则的文章。请注意,您的代码是正确的,但需要改进。


我使用了new和char*,只是为了尽可能地提高速度。 这段代码是在一个类方法中,按照我的个人风格,我使用下划线来标识类成员变量,而方法的局部变量没有前缀。 - Bocaballena

1

使用getline()可能效率低下,因为字符串缓冲区可能需要多次重新调整大小,以便从流缓冲区中附加数据。您可以通过预先设置字符串大小来使其更有效:

此外,您还可以将iostreams缓冲区的大小设置为非常大或NULL(用于无缓冲)

// Unbuffered Accesses:
fstream file;
file.rdbuf()->pubsetbuf(NULL,0);
file.open("PLOP");

// Larger Buffer
std::vector<char>  buffer(64 * 1024 * 1024);
fstream            file;
file.rdbuf()->pubsetbuf(&buffer[0],buffer.size());
file.open("PLOP");

std::string   line;
line.reserve(64 * 1024 * 1024);

while(getline(file,line))
{
    // Do Stuff.
}

这个类使用与istringstream的streambuf相关联的char*缓冲区。我直接从文件中加载原始数据到缓冲区,并使用stringstream稍后进行格式化,但这并没有改善性能。以防万一,我尝试了ifstream和pubsetbuf,但速度更慢。为什么呢? - Bocaballena

0
如果你要自己缓存文件,则建议使用非缓冲I/O进行一些测试(对于已经fopen的文件可以使用setvbuf关闭库缓冲)。
基本上,如果你要自己缓存,你需要禁用库的缓冲,因为它只会给你带来痛苦。我不知道是否有任何方法可以为STL I/O执行此操作,所以我建议降到C级别的I/O。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接