C fread () и управление буфером - PullRequest
0 голосов
/ 01 мая 2018

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

  • 12 байт C-строки
  • длина 4 байта
  • смещение 4 байта

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

Так что есть два варианта:

  • Цикл: читать 20 байтов за раз с fread() и анализировать его. Это легко, но есть недостаток в том, что в каждом цикле я вызываю функцию fread(). Это не так плохо, как системный вызов, но он по-прежнему несет издержки каждые 20 байтов. В крайнем альтернативном случае (скажем, распаковка файла по одному за раз) я мог бы делать вызов функции каждый байт .
  • Считайте большой блок за раз с помощью fread(), затем цикл: переберите мою копию по 20 байт за раз и проанализируйте ее. Разбор локальной памяти происходит быстро, но у него есть недостаток, заключающийся в том, что я «буферизирую буфер», то есть заполняю внутренний буфер fread(), чтобы я мог развернуться и memcpy() перевести его в другую структуру. Кроме того, это довольно утомительно - либо мне нужно самому разместить весь файл в ОЗУ, либо мне приходится управлять своей собственной подпрограммой заполнения-заполнения-буферизации для чтения файла порциями.

Так что я немного затруднен, потому что ни одно из решений не кажется теоретически оптимальным!

Мой вопрос: Существует ли какой-либо шаблон для использования последовательного ввода-вывода файла, который исключает оба этих недостатков? (И, пожалуйста, не комментируйте "просто профилируйте это" - вопрос не «что из этого лучше», а «есть ли третий вариант»)

1 Ответ

0 голосов
/ 02 мая 2018

Так что я немного затруднен, потому что ни одно из решений не кажется теоретически оптимальным!

и

И, пожалуйста, не комментируйте «просто профилируйте это» - вопрос не в том, «что из этого лучше», а в «есть ли третий вариант»)

Ну, у большинства систем есть альтернативы fread. Например, отображенные в память файлы, асинхронный ввод-вывод, read(). Кроме того, обычно существует несколько способов «настроить» различные типы доступа к файлам, например, для файлов, отображаемых в память, вы можете заполнить весь файл.

Однако не существует единственного метода, который можно было бы считать решением лучше . Независимо от того, какой метод вы выберете, вы получите:

просто профиль

Для метода 1 вы пишете:

Это легко, но есть недостаток в том, что я выполняю вызов функции для fread () в каждом цикле.

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

Гадать о производительности исключительно, глядя на C-код, практически невозможно в современных компьютерных системах, где экстремальные оптимизации происходят как во время компиляции, так и при выполнении.

Откуда вы знаете, что вызов fread для каждых 20 байтов является проблемой по сравнению с доступом к диску, который действительно медленный?

Опять, это возвращает вас к:

просто профиль

Ради интереса я сделал несколько простых тестов на своем Linux-компьютере, используя fread с различным количеством nmemb. Программа прочитала файл 1G и вычислила простую промежуточную сумму, используя 1 байт в каждом считанном блоке.

Результаты, когда файл холодный (т.е. не кэшируется)

nmemb        time (sec)
   1             15.68
   2             15.76
   4             15.91
   8             15.93
  20             15.60
1000             15.60

Результаты, когда файл горячий (т.е. кэширован)

nmemb        time (sec)
   1            13.90
   2             7.04
   4             3.79
   8             2.07
  20             1.09
1000             0.42

Так что в моей системе размер блока (nmemb) не имеет значения, когда файл холодный. Для большинства приложений я бы назвал холодный файл «нормальным случаем», но наверняка есть приложения, которые могут читать один и тот же файл несколько раз.

Если файл горячий (т.е. кэшируется из-за предыдущих чтений), размер блока имеет значение. Но разница быстро уменьшается по мере увеличения размера блока. Уже в 20 против 1000 разница довольно мала.

Из вышесказанного я бы заключил: просто читайте 20 байтов каждый раз, чтобы сохранить код простым.

Все приведенные выше измерения / выводы действительны только для моей конкретной системы. Другая система может генерировать совершенно разные результаты в зависимости от типа / скорости процессора, дисковой системы и ОС.

Итак, еще раз, это возвращает нас к:

просто профиль

...