медленнее, чем getline? - PullRequest
       50

медленнее, чем getline?

5 голосов
/ 12 июля 2011

Я сталкиваюсь с проблемой чтения / записи файлов (в гигах) построчно.

Читая много записей на форумах и сайтах (включая несколько SO), mmap был предложен как самый быстрый вариант для чтения / записи файлов.Тем не менее, когда я реализую свой код с использованием методов readline и mmap, mmap работает медленнее.Это верно как для чтения, так и для письма.Я тестировал файлы размером ~ 600 МБ.

Мои реализации разбирают строку за строкой, а затем разбивают строку на строки.Я представлю только файл ввода.

Вот реализация getline :

void two(char* path) {

    std::ios::sync_with_stdio(false);
    ifstream pFile(path);
    string mystring;

    if (pFile.is_open()) {
        while (getline(pFile,mystring)) {
            // c style tokenizing
        }
    }
    else perror("error opening file");
    pFile.close();
}

и вот mmap :

void four(char* path) {

    int fd;
    char *map;
    char *FILEPATH = path;
    unsigned long FILESIZE;

    // find file size
    FILE* fp = fopen(FILEPATH, "r");
    fseek(fp, 0, SEEK_END);
    FILESIZE = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    fclose(fp);

    fd = open(FILEPATH, O_RDONLY);

    map = (char *) mmap(0, FILESIZE, PROT_READ, MAP_SHARED, fd, 0);

    /* Read the file char-by-char from the mmap
     */
    char c;
    stringstream ss;

    for (long i = 0; i <= FILESIZE; ++i) {
        c = map[i];
        if (c != '\n') {
            ss << c;
        }
        else {
            // c style tokenizing
            ss.str("");
        }

    }

    if (munmap(map, FILESIZE) == -1) perror("Error un-mmapping the file");

    close(fd);

}

Я пропустил много ошибок проверки в интересах краткости.

Не верна ли моя реализация mmap и, таким образом, влияет на производительность?Возможно, mmap не подходит для моего приложения?

Спасибо за любые комментарии или помощь!

Ответы [ 4 ]

11 голосов
/ 12 июля 2011

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

В вашем цикле вы сканируете буфер по одному символу за раз, добавляя к stringstream. stringstream не знает, какова длина строки, поэтому в процессе ее приходится перераспределять несколько раз. На этом этапе вы убили любое увеличение производительности при использовании mmap - даже стандартная реализация getline позволяет избежать многократного перераспределения (используя 128-байтовый буфер в стеке в реализации GNU C ++).

Если вы хотите использовать mmap в полной мере:

  • Не копируйте свои строки. Совсем. Вместо этого скопируйте указатели прямо в буфер mmap.
  • Используйте встроенные функции, такие как strnchr или memchr, чтобы найти переводы строк; они используют свернутый вручную ассемблер и другие оптимизации, чтобы работать быстрее, чем большинство открытых циклов поиска.
6 голосов
/ 12 июля 2011

Кто бы ни сказал вам использовать mmap, он не очень много знает о современных машинах.

Преимущества mmap в производительности - это полный миф.В словах Линуса Торвальдса :

Да, память "медленная", но, черт возьми, так же, как и mmap ().

Проблемаmmap означает, что каждый раз, когда вы впервые касаетесь страницы в отображаемой области, она попадает в ядро ​​и фактически отображает страницу в ваше адресное пространство, разрушая TLB.

Попробуйтепростой бенчмарк, считывающий большой файл 8K за раз, используя read, а затем снова с mmap.(Используя один и тот же буфер 8 КБ снова и снова.) Вы почти наверняка обнаружите, что read на самом деле быстрее .

Ваша проблема никогда не заключалась в извлечении данных из ядра;это было с тем, как вы обрабатываете данные после этого.Минимизируйте работу, которую вы делаете персонажем за один раз;просто отсканируйте, чтобы найти новую строку, а затем выполните одну операцию с блоком.Лично я бы вернулся к реализации read, используя (и повторно используя) буфер, который помещается в кэш L1 (8K или около того).

Или, по крайней мере, я бы попробовал простой *Тест 1026 * против mmap, чтобы увидеть, что на вашей платформе действительно быстрее.

[Обновление]

Я нашел еще пару комплектов комментариев от мистера Торвальдса:

http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0728.html http://lkml.iu.edu/hypermail/linux/kernel/0004.0/0775.html

Краткое содержание:

И, кроме того, у вас все еще есть фактические затраты на пропуск TLB процессора и т. Д. Чего часто можно избежатьесли вы просто перечитываете в ту же область вместо того, чтобы быть чрезмерно умным с управлением памятью, чтобы избежать копирования.

memcpy () (т.е. в данном случае «read ()») - всегда во многих случаях будет быстрее, просто потому, что избегает всех дополнительных сложностей.Хотя в других случаях mmap () будет работать быстрее.

По моему опыту, чтение и обработка большого файла последовательно является одним из "многих случаев", когда использование (и повторное использование) aбуфер небольшого размера с read / write работает значительно лучше, чем mmap.

0 голосов
/ 12 июля 2011

Вы используете stringstream s для хранения идентифицированных линий.Это несопоставимо с реализацией getline, сам stringstream добавляет накладные расходы.Как и предполагалось, вы можете сохранить начало строки как char* и, возможно, длину строки (или указатель на конец строки).Тело чтения будет выглядеть примерно так:

char* str_start = map;
char* str_end;
for (long i = 0; i <= FILESIZE; ++i) {
        if (map[i] == '\n') {
            str_end = map + i;
            {
                // C style tokenizing of the string str_start to str_end
                // If you want, you can build a std::string like:
                // std::string line(str_start,str_end);
                // but note that this implies a memory copy.
            }
            str_start = map + i + 1;
        }
    }

Обратите внимание, что это гораздо эффективнее, потому что вы ничего не обрабатываете в каждом символе (в вашей версии вы добавляли символ в * 1006).*).

0 голосов
/ 12 июля 2011

Вы можете использовать memchr, чтобы найти окончания строки.Это будет намного быстрее, чем добавление к stringstream одного символа за раз.

...