用c ++快速读取文本文件

Arn*_*rne 58 c++ io performance ifstream

我目前正在用c ++编写一个程序,其中包括阅读大量的大文本文件.每行有~400.000行,极端情况下每行有4000或更多字符.仅供测试,我使用ifstream和cplusplus.com提供的实现读取其中一个文件.花了大约60秒,这太长了.现在我想知道,有没有一种直接的方法来提高阅读速度?

编辑:我使用的代码或多或少是这样的:

string tmpString;
ifstream txtFile(path);
if(txtFile.is_open())
{
    while(txtFile.good())
    {
        m_numLines++;
        getline(txtFile, tmpString);
    }
    txtFile.close();
}
Run Code Online (Sandbox Code Playgroud)

编辑2:我读的文件只有82 MB大.我主要说它可以达到4000,因为我认为可能有必要知道为了做缓冲.

编辑3:谢谢大家的答案,但考虑到我的问题似乎没有太大的改进空间.我必须使用readline,因为我想计算行数.将ifstream实例化为二进制文件也不会使读取速度更快.我将尝试尽可能地并行化它,至少应该起作用.

编辑4:显然我可以做一些事情.非常感谢你花了这么多时间,我非常感激!=)

seh*_*ehe 71

更新:请务必检查初始答案下面的(令人惊讶的)更新


内存映射文件很好地为我服务1:

#include <boost/iostreams/device/mapped_file.hpp> // for mmap
#include <algorithm>  // for std::find
#include <iostream>   // for std::cout
#include <cstring>

int main()
{
    boost::iostreams::mapped_file mmap("input.txt", boost::iostreams::mapped_file::readonly);
    auto f = mmap.const_data();
    auto l = f + mmap.size();

    uintmax_t m_numLines = 0;
    while (f && f!=l)
        if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
            m_numLines++, f++;

    std::cout << "m_numLines = " << m_numLines << "\n";
}
Run Code Online (Sandbox Code Playgroud)

这应该很快.

更新

如果它可以帮助您测试这种方法,这里是一个直接使用mmap而不是使用Boost 的版本:在Coliru上看它

#include <algorithm>
#include <iostream>
#include <cstring>

// for mmap:
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>

const char* map_file(const char* fname, size_t& length);

int main()
{
    size_t length;
    auto f = map_file("test.cpp", length);
    auto l = f + length;

    uintmax_t m_numLines = 0;
    while (f && f!=l)
        if ((f = static_cast<const char*>(memchr(f, '\n', l-f))))
            m_numLines++, f++;

    std::cout << "m_numLines = " << m_numLines << "\n";
}

void handle_error(const char* msg) {
    perror(msg); 
    exit(255);
}

const char* map_file(const char* fname, size_t& length)
{
    int fd = open(fname, O_RDONLY);
    if (fd == -1)
        handle_error("open");

    // obtain file size
    struct stat sb;
    if (fstat(fd, &sb) == -1)
        handle_error("fstat");

    length = sb.st_size;

    const char* addr = static_cast<const char*>(mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, 0u));
    if (addr == MAP_FAILED)
        handle_error("mmap");

    // TODO close fd at some point in time, call munmap(...)
    return addr;
}
Run Code Online (Sandbox Code Playgroud)

更新

通过查看GNU coreutils的来源,我发现了最后一点性能wc.令我惊讶的是,使用以下(大大简化的)代码改编自上面的内存映射文件所花费的wc 大约84%的时间:

static uintmax_t wc(char const *fname)
{
    static const auto BUFFER_SIZE = 16*1024;
    int fd = open(fname, O_RDONLY);
    if(fd == -1)
        handle_error("open");

    /* Advise the kernel of our access pattern.  */
    posix_fadvise(fd, 0, 0, 1);  // FDADVICE_SEQUENTIAL

    char buf[BUFFER_SIZE + 1];
    uintmax_t lines = 0;

    while(size_t bytes_read = read(fd, buf, BUFFER_SIZE))
    {
        if(bytes_read == (size_t)-1)
            handle_error("read failed");
        if (!bytes_read)
            break;

        for(char *p = buf; (p = (char*) memchr(p, '\n', (buf + bytes_read) - p)); ++p)
            ++lines;
    }

    return lines;
}
Run Code Online (Sandbox Code Playgroud)

1请参见例如此处的基准:如何快速解析C++中以空格分隔的浮点数?

  • 读取16kiB块会在您的进程中重用相同的4页地址空间.您将不会有TLB未命中,并且16kiB小于L1缓存.来自页面缓存的memcpy(在`read(2)`里面)非常快,而'memchr`只触及L1中很热的内存.mmap版本必须对每个页面进行故障排除,因为`mmap`没有连接所有页面(除非你使用MAP_POPULATE,但是当文件大小是RAM大小的一小部分时,这将无法正常工作). (5认同)

Lou*_*cci 10

4000*400,000 = 1.6 GB如果您的硬盘驱动器不是SSD,您可能会获得~100 MB/s的顺序读取.这只是I/O中的16秒.

由于您没有详细说明您使用的特定代码或解析这些文件的方式(您是否需要逐行读取它,系统是否有大量RAM可以将整个文件读入大型RAM缓冲区)然后解析它?)你可以做很少的事情来加快这个过程.

在按顺序读取文件时,内存映射文件不会提供任何性能改进.也许手动解析新行的大块而不是使用"getline"将提供改进.

编辑完成一些学习后(感谢@sehe).这是我可能会使用的内存映射解决方案.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

int main() {
    char* fName = "big.txt";
    //
    struct stat sb;
    long cntr = 0;
    int fd, lineLen;
    char *data;
    char *line;
    // map the file
    fd = open(fName, O_RDONLY);
    fstat(fd, &sb);
    //// int pageSize;
    //// pageSize = getpagesize();
    //// data = mmap((caddr_t)0, pageSize, PROT_READ, MAP_PRIVATE, fd, pageSize);
    data = mmap((caddr_t)0, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    line = data;
    // get lines
    while(cntr < sb.st_size) {
        lineLen = 0;
        line = data;
        // find the next line
        while(*data != '\n' && cntr < sb.st_size) {
            data++;
            cntr++;
            lineLen++;
        }
        /***** PROCESS LINE *****/
        // ... processLine(line, lineLen);
    }
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • +1 用于啤酒杯垫计算。不过,SSD 可以达到 ~500Gb/s。根据使用场景,内存映射可能更有效 (2认同)
  • @LastCoder mmap也很方便,但是:阻止分页你不访问的所有页面,隐式地以二进制模式工作,只需要_virtual_地址空间(而不是将其复制到本地缓冲区).某些文件系统驱动程序甚至可能具有零拷贝路径,尤其是在只读地图上 (2认同)

小智 10

尼尔柯克,不幸的是我无法回复您的评论(没有足够的声誉),但我对 ifstream 和 stringstream 进行了性能测试,并且逐行读取文本文件的性能完全相同。

std::stringstream stream;
std::string line;
while(std::getline(stream, line)) {
}
Run Code Online (Sandbox Code Playgroud)

这在 106MB 文件上需要 1426 毫秒。

std::ifstream stream;
std::string line;
while(ifstream.good()) {
    getline(stream, line);
}
Run Code Online (Sandbox Code Playgroud)

在同一个文件上这需要 1433 毫秒。

以下代码速度更快:

const int MAX_LENGTH = 524288;
char* line = new char[MAX_LENGTH];
while (iStream.getline(line, MAX_LENGTH) && strlen(line) > 0) {
}
Run Code Online (Sandbox Code Playgroud)

这在同一个文件上需要 884 毫秒。这只是有点棘手,因为您必须设置缓冲区的最大大小(即输入文件中每一行的最大长度)。


Jo *_* So 5

作为一个在竞争性编程方面有一点背景的人,我可以告诉你:至少对于像整数解析这样的简单事情,C 中的主要成本是锁定文件流(默认情况下是为多线程完成的)。请使用unlocked_stdio版本 ( fgetc_unlocked(), fread_unlocked())。对于 C++,常见的知识是使用std::ios::sync_with_stdio(false),但我不知道它是否和unlocked_stdio.

这里供参考的是我的标准整数解析代码。它比 scanf 快很多,正如我所说,主要是因为没有锁定流。对我来说,它和我以前使用过的最好的手工编码 mmap 或自定义缓冲版本一样快,而且没有疯狂的维护债务。

int readint(void)
{
        int n, c;
        n = getchar_unlocked() - '0';
        while ((c = getchar_unlocked()) > ' ')
                n = 10*n + c-'0';
        return n;
}
Run Code Online (Sandbox Code Playgroud)

(注意:只有在任意两个整数之间恰好有一个非数字字符时,此方法才有效)。

当然,如果可能的话,避免内存分配......