一次读取多行文件的有效方法?

我现在正在尝试处理一个大文件(几GB),所以我想使用multithreading。 该文件是多行数据,如:

data1 attr1.1 attr1.2 attr1.3 data2 attr2.1 attr2.2 attr2.3 data3 attr3.1 attr3.2 attr3.3 

我想用一个线程首先读取多行到buffer1,然后另一个线程逐行处理buffer1中的数据,而读取线程开始读取文件到buffer2。 然后,当buffer2准备好时,处理线程继续,并且读取线程再次读取到buffer1。

现在我通过使用freads为小文件(几KB)完成了处理程序部分,但我不知道如何使缓冲区包含完整的行而不是在缓冲区的末尾分割行的一部分,这是这样的:

 data1 attr1.1 attr1.2 attr1.3 data2 attr2.1 att 

另外,我发现fgetsifstream getline可以逐行读取文件,但由于它有很多IO,它会非常昂贵吗?

现在我正在努力弄清楚这是最好的方法吗? 有没有一种有效的方法一次读取多行? 任何建议表示赞赏。

C stdio和C ++ iostream函数使用缓冲I / O. 小读取只有函数调用和锁定开销,而不是read(2)系统调用开销。

在不知fgets长度的情况下, fgets必须使用缓冲区或一次读取一个字节。 幸运的是,C / C ++ I / O语义允许它使用缓冲,因此每个主流实现都可以。 (根据文档,在底层文件描述符上混合stdio和I / O会得到未定义的结果。这就是允许缓冲的原因。)

如果每个fgets需要系统调用,那么是一个问题。


您可能会发现一个线程读取行并将行放入某种对处理线程有用的数据结构很有用。

如果您不必在每一行上进行大量处理,那么在与处理相同的线程中执行I / O会将所有内容保存在该CPU的L1缓存中。 否则,数据将以I / O线程的L1结束,然后必须使其进入运行处理线程的核心的L1。


根据您对数据的处理方式,您可以通过内存映射文件来最小化复制。 或者使用fread阅读,或完全跳过stdio层,只需使用POSIX open / read ,如果您不需要您的代码可移植。 扫描缓冲区以获取换行符的开销比stdio函数的开销少。

您可以通过将缓冲区复制到缓冲区的前面来处理缓冲区末尾的剩余行,并使用减小的缓冲区大小调用下一个fread 。 (或者,使你的缓冲区大约比你的fread调用大1k,所以你总是可以读取内存和文件系统页面大小的倍数(通常是4kiB),除非该行的尾部是> 1k。)

或者使用循环缓冲区 ,但从循环缓冲区读取意味着每次触摸它时都要检查环绕。

这一切都取决于你之后要做的事情:你需要保留一行副本吗? 您打算将输入处理为std :: strings吗? 等等…

这里有一些可以帮助你进一步发展的一般性评论:

  • istream::getline()fgets()是缓冲操作。 因此I / O已经减少,您可以认为性能已经正确。

  • std::getline()也是缓冲的。 然而,如果你不需要处理std::string那么函数将花费你相当多的内存分配/释放,这可能会影响性能

  • 如果你能负担得起大缓冲区,像read()fread()这样的Bloc操作可以实现规模经济。 如果您以一次性方式使用数据(因为您可以避免复制数据并直接在缓冲区中工作),这可能会特别有效,但代价是额外的复杂性。

但是,所有这些考虑因素都不应忘记,您使用的库实现会严重影响性能。

我已经做了一些非正式的基准测试,以你所显示的格式读取数百万行:*在我的PC上使用MSVC2015, read()速度是fgets()两倍,几乎是std::string 4倍。 *在CodingGround上使用GCC,使用O3编译, fgets()和两个getline()大致相同,而read()更慢。

这里是完整的代码,如果你想玩。

这里的代码向您展示如何移动缓冲区arround。

 int nr=0; // number of bytes read bool last=false; // last (incomplete) read while (!last) { // here nr conains the number of bytes kept from incomplete line last = !ifs.read(buffer+nr, szb-nr); nr = nr+ifs.gcount(); char *s, *p = buffer, *pe = p + nr; do { // process complete lines in buffer for (s = p; p != pe && *p != '\n'; p++) ; if (p != pe || (p == pe && last)) { if (p != pe) *p++ = '\0'; lines++; // TO DO: here s is a null terminated line to process sln += strlen(s); // (dummy operatio for the example) } } while (p != pe); // until eand of buffer is reached std::copy(s, pe, buffer); // copy last (incoplete) line to begin of buffer nr = pe - s; // and prepare the info for the next iteration }