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

时间:2015-10-30 17:10:29

标签: c++ c multithreading file-read

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

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,它会非常昂贵吗?

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

2 个答案:

答案 0 :(得分:1)

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

在不知道行长度的情况下,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 。)

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

答案 1 :(得分:0)

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

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

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

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

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

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

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

如果您想玩游戏,请full code

这里的代码向您展示如何移动缓冲区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
}