使用缓冲区在C++中逐行读取大型文本文件

12

我需要在C++中逐行从磁盘读取一个35G的大文件。目前我是这样做的:

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

但它只能提供我大约2MB /秒的性能,尽管文件管理器以100Mb / s的速度复制文件。我猜测 getline() 没有正确地进行缓冲。请提出某种缓冲逐行读取的方法。

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


1
getline 不进行任何缓冲,istream 进行了缓冲。为什么必须按行读取?为什么不一次性读取几百万行? - n. m.
2
你为什么认为瓶颈在于 getline 而不是 process - Igor Tandetnik
3
你是否使用了优化编译选项?对于g++和clang,请使用-O2或-O3,对于Visual C++,请使用Release构建。 - Blastfurnace
@IgorTandetnik,请看我的更新。 - Stepan Yakovenko
1
尝试不使用 tellg。你应该使用纯 getline,速度会更快。 - Adam
显示剩余6条评论
3个回答

17
您无法通过标准IO流接近线速。无论是缓冲还是不缓冲,任何解析都会使您的速度大幅下降。我对由每行两个int和一个double组成的数据文件进行了实验(使用Ivy Bridge芯片和SSD):
- 各种IO流的组合:约为10 MB/s。纯解析(f >> i1>> i2>> d)比将getline转换为字符串并跟随sstringstream解析更快。 - 类似fscanf的C文件操作约为40 MB/s。 - 不解析的getline:180 MB/s。 - fread:500-800 MB/s(取决于文件是否被操作系统缓存)。
输入/输出不是瓶颈,解析才是。换句话说,您的处理过程可能是缓慢的点。
因此,我编写了一个并行解析器。它由任务组成(使用TBB管道):
- fread大块(一次一个这样的任务) - 重新排列块,使行不在块之间分割(一次一个这样的任务) - 解析块(许多此类任务)
我可以有无限的解析任务,因为我的数据毫无顺序。如果您的数据没有顺序,那么这可能对您没有价值。
这种方法在4核IvyBridge芯片上可获得约100 MB/s的速度。

@user2313838 是的,它被缓存了。我的代码看起来也很像提问者的。 - Adam
@ArthurKushman,那只有22 MB/s,你应该能够获得比这更多的速度。 - Adam
这句话的意思是什么:“使用标准IO流,你不可能接近线速度”?即使我们从OP的原始代码中删除“process”,它仍然会很慢。 - starriet
@starriet,它的意思与您在第二句话中所说的完全相同。行速度是存储设备能够读取的速度,在这个例子中大约为500 MB/s。IO流只能以大约10 MB/s的速度解析,因此解析是瓶颈(非常明显)。 - Adam
@Adam 感谢您的评论。那么我认为您回答中的这部分内容:“process 可能是您的瓶颈”应该改为类似于“getline 是您的瓶颈”,对吗? - starriet
显示剩余5条评论

4

我已经将我的Java项目中的缓冲代码翻译成了中文,它可以满足我的需求。我不得不使用一些定义来克服M$VC 2010编译器中tellg的问题,该编译器在处理大文件时总是给出错误的负值。尽管这个算法做了一些无用的new[]操作,但它可以提供所需的速度,约为每秒100MB。

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);
}

0
使用一个行解析器或者自己编写一个。这里有一个在sourceforge上的示例http://tclap.sourceforge.net/,如果需要的话可以放入缓冲区中。

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