Почему последовательное чтение большого файла строка за строкой с mmap и madvise последовательно медленнее, чем fgets? - PullRequest
3 голосов
/ 19 мая 2011

Обзор

У меня есть программа, существенно ограниченная IO, и я пытаюсь ускорить ее.Использование mmap казалось хорошей идеей, но на самом деле это снижает производительность по сравнению с использованием серии вызовов fgets.

Некоторый демо-код

Я сжал демки до самых основных, тестируя файл размером 800 Мб с примерно 3,5 миллионами строк:

С помощью fgets:

char buf[4096];
FILE * fp = fopen(argv[1], "r");

while(fgets(buf, 4096, fp) != 0) {
    // do stuff
}
fclose(fp);
return 0;

Время выполнения для файла 800 МБ:

[juhani@xtest tests]$ time ./readfile /r/40/13479/14960 

real    0m25.614s
user    0m0.192s
sys 0m0.124s

Версия mmap:

struct stat finfo;
int fh, len;
char * mem;
char * row, *end;
if(stat(argv[1], &finfo) == -1) return 0;
if((fh = open(argv[1], O_RDONLY)) == -1) return 0;

mem = (char*)mmap(NULL, finfo.st_size, PROT_READ, MAP_SHARED, fh, 0);
if(mem == (char*)-1) return 0;
madvise(mem, finfo.st_size, POSIX_MADV_SEQUENTIAL);
row = mem;
while((end = strchr(row, '\n')) != 0) {
    // do stuff
    row = end + 1;
}
munmap(mem, finfo.st_size);
close(fh);

Время выполнения варьируется весьма незначительно,но не быстрее, чем fgets:

[juhani@xtest tests]$ time ./readfile_map /r/40/13479/14960

real    0m28.891s
user    0m0.252s
sys 0m0.732s
[juhani@xtest tests]$ time ./readfile_map /r/40/13479/14960

real    0m42.605s
user    0m0.144s
sys 0m0.472s

Другие заметки

  • Наблюдая за процессом, запущенным сверху, версия memmapped вызвала несколько тысяч сбоев страниц по пути.
  • Процессор и использование памяти очень низки для версии fgets.

Вопросы

  • Почему это так?Это просто потому, что доступ к буферизованному файлу, реализованный fopen / fgets, лучше, чем агрессивная предварительная выборка, которую mmap с madvise POSIX_MADV_SEQUENTIAL?
  • Есть ли альтернативный способ сделать это быстрее (кроме сжатия на лету/ декомпрессия для переноса нагрузки ввода-вывода на процессор)?Глядя на время выполнения 'wc -l' в том же файле, я предполагаю, что это может быть не так.

Ответы [ 2 ]

4 голосов
/ 19 мая 2011

POSIX_MADV_SEQUENTIAL является лишь подсказкой для системы и может полностью игнорироваться конкретной реализацией POSIX.

Разница между вашими двумя решениями заключается в том, что mmap требует, чтобы файл был полностью сопоставлен с виртуальным адресным пространством, тогда как fgets полностью выполняет IO в пространстве ядра и просто копирует страницы в буфер, который не ' t изменить.

Это также имеет больший потенциал для перекрытия, поскольку IO выполняется каким-то потоком ядра.

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

3 голосов
/ 20 января 2013

Чтение справочных страниц mmap показывает, что сбои страниц можно предотвратить, добавив MAP_POPULATE к mmap флагам:

MAP_POPULATE (since Linux 2.5.46): заполнить страницу (до сбоя)столы для картографии.Для сопоставления файлов это вызывает упреждающее чтение файла.Более поздние обращения к сопоставлению не будут блокироваться ошибками страниц.

Таким образом, поток предварительной загрузки, отказывающий страницу (как предложено Йенсом), устареет.

Редактировать: Прежде всего, тесты, которые вы выполняете, должны выполняться с очищенным кешем страниц, чтобы получить значимые результаты:

    echo 3 | sudo tee /proc/sys/vm/drop_caches

Дополнительно: MADV_WILLNEED рекомендация с madvise будет предварительно вызывать ошибкутребуемые страницы в (такие же, как POSIX_FADV_WILLNEED с fadvise).К сожалению, в настоящее время эти вызовы блокируются до тех пор, пока не будут обнаружены ошибки на запрошенных страницах, даже если документация говорит по-другому.Однако в настоящее время существуют исправления ядра, которые помещают запросы до сбоя в рабочую очередь ядра, чтобы сделать эти вызовы асинхронными, как и следовало ожидать, - что делает отдельный поток пользовательского пространства с упреждающим чтением устаревшим.

...