"Оптимальная" буферизация ввода - вывода-задача программиста или ядра?



Моя задача очень проста: прочитать и разобрать большой файл на C++ в Linux. Есть два способа:

  1. Разбирать байт за байтом.

    while(/*...*/) {
            ... = fgetc(...);
            /* do something with the char */
    }
    
  2. Разбирать буфер за буфером.

    while(/*...*/) {
            char buffer[SOME_LARGE_NUMBER];
            fread(buffer, SOME_LARGE_NUMBER, 1, ...);
            /* parse the buffer */
    }
    

Теперь мне проще разбирать байт за байтом (нет проверки, насколько полон буфер и т. д.). Однако я слышал, что чтение больших произведений более эффективно.

Что такое философия? Является ли" оптимальная " буферизация задачей ядра, поэтому она уже буферизуется, когда я вызываю fgetc()? Или он предложил, чтобы я справился с этим, чтобы получить максимальную эффективность?

Кроме того, помимо всей философии: какова реальность на Linux здесь?

47   5  

5 ответов:

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

Я сделал несколько быстрых и грязных таймингов для моей локальной системы (очевидно, YMMV).

Я выбрал файл ~200k и суммировал каждый байт. Я проделал это 20000 раз, чередуя каждые 1000 циклов между чтением с использованием fgetc() и чтением с использованием fread(). Я рассчитал каждые 1000 циклов как единый комок. Я скомпилировал сборку релиза с включенной оптимизацией.

Вариант цикла fgetc() был последовательно45x медленнее, чем цикл fread().

После запроса в комментариях я также сравнил getc(), а также изменил буфер stdio. Не было никаких заметных изменений в производительности.

Буферstdio не является частью ядра. Это часть пользовательского пространства.

Однако вы можете повлиять на размер этого буфера, используяsetbuf . Если этот буфер недостаточно полон, библиотека stdio заполнит его, выдав системную функцию read.

Так что это не будет иметь значения, используя fgetc или fread это условия переключения между ядром и пользователем.

На самом деле это не имеет значения. Даже из SSD накладные расходы на ввод-вывод уменьшают время, затраченное на буферизацию. Конечно, теперь это микросекунды вместо миллисекунд, но вызовы функций измеряются в наносекундах.

Причиной медлительности fgetc является не количество вызовов функций, а количество системных вызовов. fgetc часто реализуется как int fgetc(FILE *fp) { int ch; return (fread(&ch,1,1,fp)==1?ch:-1); }

Несмотря на то, что сам fread может буферизировать 64k или 1k, накладные расходы на системные вызовы делают разницу по сравнению с, например,

 int fgetc_buffered(FILE *fp) {
     static int head=0,tail=0; 
     static unsigned char buffer[1024];
     if (head>tail) return buffer[tail++];
     tail=0;head=fread(buffer,1,1024,fp);
     if (head<=0) return -1;
     return buffer[tail++];
 }

Процедуры stdio выполняют буферизацию пространства пользователя. Когда вы вызываете getc, fgetc, fread, они извлекают данные из буфера пользовательского пространства stdio. Когда буфер пуст, stdio использует вызов чтения ядра для получения дополнительных данных.

Люди, которые разрабатывают файловые системы, знают, что доступ к диску (в основном, поиск) очень дорог. Таким образом, даже если stdio использует размер блока 512 байт, файловая система может использовать размер блока 4 КБ, и ядро будет читать файл по 4 КБ за раз.

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

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

Использование mmap не приведет получите преимущество чтения ядра вперед.

    Ничего не найдено.

Добавить ответ:
Отменить.