очень быстрая обработка текстовых файлов (C ++) - PullRequest
10 голосов
/ 14 ноября 2011

Я написал приложение, которое обрабатывает данные на GPU.Код работает хорошо, но у меня проблема в том, что считывающая часть входного файла (~ 3 ГБ, текст) является узким местом моего приложения.(Чтение с жесткого диска быстрое, но обработка строка за строкой медленная).

Я читаю строку с помощью getline () и копирую строку 1 в вектор, строку 2 в вектор и пропускаю строки 3 и4. И так далее для остальных 11 миллионов строк.

Я попробовал несколько подходов, чтобы получить файл в лучшее возможное время:

Самый быстрый метод, который я нашел, использует boost :: iostreams:: stream

Другие были:

  • Чтение файла как gzip, чтобы минимизировать ввод-вывод, но медленнее, чем его непосредственное чтение.
  • скопировать файл в оперативную память путем чтения (filepointer, chararray, length) и обработать его с помощью цикла, чтобы различать строки (также медленнее, чем boost)

Любые предложения, как сделатьработать быстрее?

void readfastq(char *filename, int SRlength, uint32_t blocksize){
    _filelength = 0; //total datasets (each 4 lines)
    _SRlength = SRlength; //length of the 2. line
    _blocksize = blocksize;

    boost::iostreams::stream<boost::iostreams::file_source>ins(filename);
    in = ins;

    readNextBlock();
}


void readNextBlock() {
    timeval start, end;
    gettimeofday(&start, 0);

    string name;
    string seqtemp;
    string garbage;
    string phredtemp;

    _seqs.empty();
    _phred.empty();
    _names.empty();
    _filelength = 0;

            //read only a part of the file i.e the first 4mio lines
    while (std::getline(in, name) && _filelength<_blocksize) {
        std::getline(in, seqtemp);
        std::getline(in, garbage);
        std::getline(in, phredtemp);

        if (seqtemp.size() != _SRlength) {
            if (seqtemp.size() != 0)
                printf("Error on read in fastq: size is invalid\n");
        } else {
            _names.push_back(name);

            for (int k = 0; k < _SRlength; k++) {

                //handle special letters
                                    if(seqtemp[k]== 'A') ...
                                    else{
                _seqs.push_back(5);
                                    }

            }
            _filelength++;
        }
    }

РЕДАКТИРОВАТЬ:

Исходный файл можно загрузить в https://docs.google.com/open?id=0B5bvyb427McSMjM2YWQwM2YtZGU2Mi00OGVmLThkODAtYzJhODIzYjNhYTY2

Я изменил функцию readfastq, чтобы прочитатьфайл, из-за некоторых проблем с указателем.Поэтому, если вы звоните readfastq, blocksize (в строках) должно быть больше, чем количество строк для чтения.

РЕШЕНИЕ:

Я нашел решение, который получает время для чтения в файле от 60 секунд до 16 секунд.Я удалил внутренний цикл, который обрабатывает специальные символы, и сделал это в GPU.Это уменьшает время чтения и только минимально увеличивает время работы графического процессора.

Спасибо за ваши предложения.

void readfastq(char *filename, int SRlength) {
    _filelength = 0;
    _SRlength = SRlength;

    size_t bytes_read, bytes_expected;

    FILE *fp;
    fp = fopen(filename, "r");

    fseek(fp, 0L, SEEK_END); //go to the end of file
    bytes_expected = ftell(fp); //get filesize
    fseek(fp, 0L, SEEK_SET); //go to the begining of the file

    fclose(fp);

    if ((_seqarray = (char *) malloc(bytes_expected/2)) == NULL) //allocate space for file
        err(EX_OSERR, "data malloc");


    string name;
    string seqtemp;
    string garbage;
    string phredtemp;

    boost::iostreams::stream<boost::iostreams::file_source>file(filename);


    while (std::getline(file, name)) {
        std::getline(file, seqtemp);
        std::getline(file, garbage);
        std::getline(file, phredtemp);

        if (seqtemp.size() != SRlength) {
            if (seqtemp.size() != 0)
                printf("Error on read in fastq: size is invalid\n");
        } else {
            _names.push_back(name);

            strncpy( &(_seqarray[SRlength*_filelength]), seqtemp.c_str(), seqtemp.length()); //do not handle special letters here, do on GPU

            _filelength++;
        }
    }
}

Ответы [ 5 ]

7 голосов
/ 14 ноября 2011

Сначала вместо чтения файла в память вы можете работать с сопоставлениями файлов. Вы просто должны построить свою программу как 64-битную, чтобы разместить 3 ГБ виртуального адресного пространства (для 32-битного приложения только 2 ГБ доступно в пользовательском режиме). Или же вы можете отобразить и обработать ваш файл по частям.

Далее мне кажется, что ваше узкое место - это "копирование линии в вектор". Работа с векторами включает динамическое выделение памяти (операции с кучей), что в критическом цикле очень серьезно влияет на производительность). Если это так - либо избегайте использования векторов, либо убедитесь, что они объявлены вне цикла. Последнее помогает, потому что когда вы перераспределяете / очищаете векторы, они не освобождают память.

Разместите свой код (или его часть) для получения дополнительных предложений.

EDIT:

Кажется, что все ваши узкие места связаны с управлением строками.

  • std::getline(in, seqtemp); чтение в std::string имеет дело с динамическим распределением памяти.
  • _names.push_back(name); Это еще хуже. Сначала std::string помещается в vector на значение . Означает - строка копируется, следовательно, происходит другое динамическое распределение / освобождение. Более того, когда в конечном итоге vector перераспределяется изнутри - все содержащиеся в нем строки снова копируются со всеми вытекающими последствиями.

Я рекомендую не использовать ни стандартные функции ввода / вывода для файлов (Stdio / STL), ни std::string. Для достижения лучшей производительности вы должны работать с указателями на строки (а не на скопированные строки), что возможно, если вы отобразите весь файл. Кроме того, вам придется реализовать разбор файлов (деление на строки).

Как в этом коде:

class MemoryMappedFileParser
{
    const char* m_sz;
    size_t m_Len;

public:

    struct String {
        const char* m_sz;
        size_t m_Len;
    };

    bool getline(String& out)
    {
        out.m_sz = m_sz;

        const char* sz = (char*) memchr(m_sz, '\n', m_Len);
        if (sz)
        {
            size_t len = sz - m_sz;

            m_sz = sz + 1;
            m_Len -= (len + 1);

            out.m_Len = len;

            // for Windows-format text files remove the '\r' as well
            if (len && '\r' == out.m_sz[len-1])
                out.m_Len--;
        } else
        {
            out.m_Len = m_Len;

            if (!m_Len)
                return false;

            m_Len = 0;
        }

        return true;
    }

};
5 голосов
/ 14 ноября 2011

если _seqs и _names равны std::vectors и вы можете угадать окончательный размер их перед обработкой целых 3 ГБ данных, вы можете использовать reserve, чтобы избежать большей части памяти перераспределение во время отталкивания новых элементов в цикле.

Вам следует учитывать тот факт, что векторы эффективно создают еще одну копию частей файла в основной памяти. Поэтому, если у вас недостаточно основной памяти для хранения текстового файла, а также вектора и его содержимого, вы, вероятно, в конечном итоге столкнетесь с рядом сбоев страниц, которые также окажут значительное влияние на скорость вашей программы.

2 голосов
/ 14 ноября 2011

Общие предложения:

  • Используйте самый простой, простой и понятный подход,
  • Мера
  • Мера
  • Мера

Тогда, если все остальное терпит неудачу:

  • Считать необработанные байты (read(2)) в выровненных по частям фрагментах Делайте это последовательно, чтобы упреждающее чтение ядра использовалось в ваших интересах.
  • Повторно использовать тот же буфер для минимизации очистки кеша.
  • Избегайте копирования данных, разбора на месте, передачи указателей (и размеров).

mmap(2) -ing [части файла] - это другой подход. Это также позволяет избежать копирования ядра пользователя.

2 голосов
/ 14 ноября 2011

Вы, очевидно, используете <stdio.h>, поскольку используете getline.

Возможно, fopen -ing файл с fopen(path, "rm"); может помочь, потому что m говорит (это расширение GNU)использовать mmap для чтения.

Возможно, также может помочь установка большого буфера (то есть половина мегабайта) с помощью setbuffer.

Возможно, с использованием readahead Системный вызов (возможно, в отдельном потоке) может помочь.

Но все это догадки.Вы должны действительно измерить вещи.

0 голосов
/ 14 ноября 2011

В зависимости от скорости вашего диска может помочь использование очень быстрого алгоритма сжатия, например fastlz (есть как минимум два других, которые могут быть более эффективными, но под лицензией GPL, поэтому может возникнуть проблема с лицензией).

Кроме того, используя структуры данных и функции C ++, можно увеличить скорость, поскольку вы можете , может * добиться лучшей оптимизации во время компиляции. Идти по пути С не всегда быстро! В некоторых плохих условиях, используя char *, вам нужно проанализировать всю строку, чтобы достичь \ 0, приводящих к ужасным результатам.

Для анализа ваших данных использование boost :: spirit :: qi также, вероятно, является наиболее оптимизированным подходом http://alexott.blogspot.com/2010/01/boostspirit2-vs-atoi.html

...