Чтение огромного текстового файла по строкам в С++ с буферизацией

Мне нужно прочитать огромный файл 35G из диска по строкам на С++. В настоящее время я делаю это следующим образом:

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

Но это дает мне производительность около 2 МБ/с, хотя файловый менеджер копирует файл со скоростью 100 Мбит/с. Я думаю, что getline() не выполняет буферизацию правильно. Пожалуйста, предложите какой-то буферизованный линейный подход к чтению.

UPD: process() не является узким местом, код без процесса() работает с той же скоростью.

Ответ 1

Я перевел свой собственный код буферизации из моего проекта Java, и он делает то, что мне нужно. Мне пришлось поставить определения, чтобы преодолеть проблемы с Tellg компилятором M $ VC 2010, который всегда дает неправильные отрицательные значения на больших файлах. Этот алгоритм дает желаемую скорость ~ 100 МБ/с, хотя он делает некоторые бесполезные новые [].

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

Ответ 2

Вы не приблизитесь к скорости линии со стандартными потоками ввода-вывода. Буферизация или нет, почти ЛЮБОЙ разбор убьет вашу скорость на порядки. Я провел эксперименты с файлами данных, состоящими из двух целочисленных значений и двойного числа на строку (чип Ivy Bridge, SSD):

  • IO-потоки в различных комбинациях: ~ 10 МБ/с. Чистый синтаксический анализ (f >> i1 >> i2 >> d) выполняется быстрее, чем getline, в строку с последующим анализом sstringstream.
  • Операции с файлами C, такие как fscanf, получают около 40 МБ/с.
  • getline без разбора: 180 МБ/с.
  • fread: 500–800 МБ/с (в зависимости от того, был ли файл кэширован ОС).

Ввод/вывод не является узким местом, разбор есть. Другими словами, ваш process, скорее всего, ваша медленная точка.

Поэтому я написал параллельный парсер. Он состоит из задач (с использованием конвейера TBB):

  1. fread большие куски (одна такая задача за раз)
  2. переставьте чанки так, чтобы строка не разделялась на чанки (одна такая задача за раз)
  3. разбирать чанк (много таких задач)

У меня могут быть неограниченные задачи разбора, потому что мои данные в любом случае неупорядочены. Если нет, то это может не стоить того. Такой подход дает мне около 100 МБ/с на 4-ядерном чипе IvyBridge.

Ответ 3

Используйте синтаксический анализатор строк или напишите его. здесь приведен пример в sourceforge http://tclap.sourceforge.net/ и при необходимости помещать в буфер.