用带缓冲的c++逐行读取巨大的文本文件

read huge text file line by line in C++ with buffering

本文关键字:巨大 文本 文件 读取 c++ 缓冲 逐行      更新时间:2023-10-16

我需要在c++中一行一行地从光盘读取巨大的35G文件。目前我是这样做的:

ifstream infile("myfile.txt");
string line;
while (true) {
    if (!getline(infile, line)) break;
    long linepos = infile.tellg();
    process(line,linepos);
}

但是它给了我大约2MB/s的性能,尽管文件管理器以100Mb/s的速度复制文件。我猜getline()没有正确地做缓冲。请提出一种逐行缓冲的阅读方法。

UPD: process()不是瓶颈,没有process()的代码工作速度相同。

使用标准IO流,您将无法获得接近线路速度的任何地方。不管有没有缓冲,几乎任何解析都会降低你的速度。我在由两个int和一个double组成的数据文件上做了实验(Ivy Bridge芯片,SSD):

  • 各种组合的IO流:~ 10mb/s。纯解析(f >> i1 >> i2 >> d)比getline解析字符串然后sstringstream解析更快。
  • C文件操作,如fscanf得到大约40 MB/s。
  • getline无解析:180 MB/s
  • fread: 500- 800mb/s(取决于文件是否被OS缓存)。

I/O不是瓶颈,解析才是。换句话说,您的process可能是您的慢点。

所以我写了一个并行解析器。它由任务组成(使用TBB管道):
  1. fread大块(一次一个这样的任务)
  2. 重新排列块,这样一行不会在块之间分裂(一次一个这样的任务)
  3. 解析块(许多这样的任务)

我可以有无限的解析任务,因为我的数据是无序的。如果你的不是,那么这对你来说可能不值得。这种方法使我在4核IvyBridge芯片上获得大约100 MB/s的速度。

我已经从我的java项目中翻译了我自己的缓冲代码,它做了我需要的。我必须用定义来克服m$ VC 2010编译器提示的问题,它总是在大文件上给出错误的负值。该算法给出了理想的速度~100MB/s,尽管它做了一些无用的新[]。

void readFileFast(ifstream &file, void(*lineHandler)(char*str, int length, __int64 absPos)){
        int BUF_SIZE = 40000;
        file.seekg(0,ios::end);
        ifstream::pos_type p = file.tellg();
#ifdef WIN32
        __int64 fileSize = *(__int64*)(((char*)&p) +8);
#else
        __int64 fileSize = p;
#endif
        file.seekg(0,ios::beg);
        BUF_SIZE = min(BUF_SIZE, fileSize);
        char* buf = new char[BUF_SIZE];
        int bufLength = BUF_SIZE;
        file.read(buf, bufLength);
        int strEnd = -1;
        int strStart;
        __int64 bufPosInFile = 0;
        while (bufLength > 0) {
            int i = strEnd + 1;
            strStart = strEnd;
            strEnd = -1;
            for (; i < bufLength && i + bufPosInFile < fileSize; i++) {
                if (buf[i] == 'n') {
                    strEnd = i;
                    break;
                }
            }
            if (strEnd == -1) { // scroll buffer
                if (strStart == -1) {
                    lineHandler(buf + strStart + 1, bufLength, bufPosInFile + strStart + 1);
                    bufPosInFile += bufLength;
                    bufLength = min(bufLength, fileSize - bufPosInFile);
                    delete[]buf;
                    buf = new char[bufLength];
                    file.read(buf, bufLength);
                } else {
                    int movedLength = bufLength - strStart - 1;
                    memmove(buf,buf+strStart+1,movedLength);
                    bufPosInFile += strStart + 1;
                    int readSize = min(bufLength - movedLength, fileSize - bufPosInFile - movedLength);
                    if (readSize != 0)
                        file.read(buf + movedLength, readSize);
                    if (movedLength + readSize < bufLength) {
                        char *tmpbuf = new char[movedLength + readSize];
                        memmove(tmpbuf,buf,movedLength+readSize);
                        delete[]buf;
                        buf = tmpbuf;
                        bufLength = movedLength + readSize;
                    }
                    strEnd = -1;
                }
            } else {
                lineHandler(buf+ strStart + 1, strEnd - strStart, bufPosInFile + strStart + 1);
            }
        }
        lineHandler(0, 0, 0);//eof
}
void lineHandler(char*buf, int l, __int64 pos){
    if(buf==0) return;
    string s = string(buf, l);
    printf(s.c_str());
}
void loadFile(){
    ifstream infile("file");
    readFileFast(infile,lineHandler);
}

使用行解析器或编写相同的内容。下面是sourceforge http://tclap.sourceforge.net/中的一个示例,必要时放入缓冲区。