Как вы определяете идеальный размер буфера при использовании FileInputStream? - PullRequest
138 голосов
/ 25 октября 2008

У меня есть метод, который создает MessageDigest (хеш) из файла, и мне нужно сделать это для большого количества файлов (> = 100 000). Насколько большой я должен сделать буфер, используемый для чтения из файлов, чтобы максимизировать производительность?

Большинство из нас знакомы с базовым кодом (который я повторю здесь на всякий случай):

MessageDigest md = MessageDigest.getInstance( "SHA" );
FileInputStream ios = new FileInputStream( "myfile.bmp" );
byte[] buffer = new byte[4 * 1024]; // what should this value be?
int read = 0;
while( ( read = ios.read( buffer ) ) > 0 )
    md.update( buffer, 0, read );
ios.close();
md.digest();

Какой идеальный размер буфера для максимизации пропускной способности? Я знаю, что это зависит от системы, и я уверен, что это зависит от ОС, FileSystem, и HDD, и, возможно, в миксе есть другое аппаратное / программное обеспечение.

(Я должен отметить, что я немного новичок в Java, так что это может быть просто вызов Java API, о котором я не знаю.)

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

Редактировать: В приведенном выше коде отсутствуют такие вещи, как try..catch, чтобы уменьшить размер поста

Ответы [ 9 ]

193 голосов
/ 26 октября 2008

Оптимальный размер буфера зависит от нескольких факторов: размер блока файловой системы, размер кэша ЦП и задержка кэша.

Большинство файловых систем сконфигурировано для использования блоков размером 4096 или 8192. Теоретически, если вы конфигурируете размер буфера так, что вы читаете на несколько байтов больше, чем дисковый блок, операции с файловой системой могут быть крайне неэффективными ( т. е. если вы сконфигурировали буфер для чтения 4100 байт за раз, каждая операция чтения потребовала бы 2 блока чтения файловой системой). Если блоки уже находятся в кеше, вы платите цену RAM -> L3 / L2 задержка кеша. Если вам не повезло, а блоки еще не находятся в кеше, вы также платите за задержку диска-> ОЗУ.

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

Теперь, это типично смещено в типичном сценарии потоковой передачи, потому что блок, который читается с диска, все еще будет в памяти, когда вы нажмете следующее чтение (в конце концов, мы делаем последовательные чтения здесь) - так при следующем чтении вы платите цену ОЗУ -> L3 / L2 кеша, но не задержку диска -> ОЗУ. С точки зрения порядка величины задержка диска-> ОЗУ настолько медленная, что значительно перекрывает любую другую задержку, с которой вы можете иметь дело.

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

Здесь есть тонна условий и исключений - сложности системы на самом деле довольно ошеломляют (просто получить контроль над передачей в кэш L3 -> L2 невероятно сложно, и она меняется с каждым Тип процессора).

Это приводит к ответу «реального мира»: если ваше приложение на 99%, установите размер кэша на 8192 и продолжайте (еще лучше, выберите инкапсуляцию вместо производительности и используйте BufferedInputStream, чтобы скрыть детали). Если вы находитесь в 1% приложений, которые сильно зависят от пропускной способности диска, разработайте свою реализацию, чтобы вы могли поменять различные стратегии взаимодействия с диском и предоставили ручки и наборы, чтобы позволить вашим пользователям тестировать и оптимизировать (или придумать некоторые самооптимизирующаяся система).

15 голосов
/ 25 октября 2008

Да, это, вероятно, зависит от разных вещей - но я сомневаюсь, что это будет иметь большое значение. Я склоняюсь к выбору 16K или 32K в качестве хорошего баланса между использованием памяти и производительностью.

Обратите внимание, что в коде должен быть блок try / finally, чтобы убедиться, что поток закрыт, даже если выдается исключение.

7 голосов
/ 26 октября 2008

В большинстве случаев это не так уж важно. Просто выберите хороший размер, например 4K или 16K, и придерживайтесь его. Если вы уверены, , что это узкое место в вашем приложении, то вы должны начать профилирование, чтобы найти оптимальный размер буфера. Если вы выберете слишком маленький размер, вы будете тратить время на дополнительные операции ввода-вывода и дополнительные вызовы функций. Если вы выберете слишком большой размер, вы начнете видеть много пропусков кэша, которые действительно замедлят вас. Не используйте буфер больше, чем ваш размер кэша L2.

4 голосов
/ 26 октября 2008

Чтение файлов с использованием Java NIO FileChannel и MappedByteBuffer, скорее всего, приведет к решению, которое будет намного быстрее, чем любое решение, включающее FileInputStream. В основном, отображайте в памяти большие файлы и используйте прямые буферы для маленьких.

4 голосов
/ 26 октября 2008

Вы можете использовать BufferedStreams / reader и затем использовать их размеры буфера.

Я полагаю, что BufferedXStreams использует 8192 в качестве размера буфера, но, как сказал Овидиу, вам, вероятно, следует выполнить тест для целого ряда параметров. Это действительно будет зависеть от файловой системы и конфигурации диска относительно того, каковы лучшие размеры.

4 голосов
/ 26 октября 2008

В идеальном случае у нас должно быть достаточно памяти для чтения файла за одну операцию чтения. Это было бы лучшим результатом, потому что мы позволяем системе управлять файловой системой, единицами распределения и жесткими дисками по своему усмотрению. На практике вам повезло заранее знать размеры файлов, просто используйте средний размер файла, округленный до 4 КБ (единица выделения по умолчанию в NTFS). И самое главное: создайте тест для тестирования нескольких вариантов.

1 голос
/ 05 января 2017

В источнике BufferedInputStream вы найдете: private static int DEFAULT_BUFFER_SIZE = 8192;
Так что вы можете использовать это значение по умолчанию.
Но если вы сможете узнать больше информации, вы получите более ценные ответы.
Например, ваш adsl может иметь буфер 1454 байта, потому что полезная нагрузка TCP / IP. Для дисков вы можете использовать значение, соответствующее размеру блока вашего диска.

1 голос
/ 26 октября 2008

Как уже упоминалось в других ответах, используйте BufferedInputStreams.

После этого, я думаю, размер буфера не имеет большого значения. Либо программа связана с вводом-выводом, и увеличение размера буфера по сравнению с BIS по умолчанию не окажет большого влияния на производительность.

Или программа связана с ЦП внутри MessageDigest.update (), и большая часть времени не тратится на код приложения, поэтому его настройка не поможет.

(Хм ... с несколькими ядрами, темы могут помочь.)

0 голосов
/ 05 января 2017

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

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

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

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

...