it-roy-ru.com

Как вы определяете идеальный размер буфера при использовании FileInputStream?

У меня есть метод, который создает 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, чтобы сделать сообщение меньше

132
ARKBAN

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

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

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

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

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

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

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

187
Kevin Day

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

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

14
Jon Skeet

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

7
Adam Rosenfield

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

4
Ovidiu Pacurar

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

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

4
John Gardner

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

4
Alexander

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

1
GoForce5500

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

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

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

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

1
Maglob

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

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

Также обычно выбирают степень 2 для размера буфера, поскольку большинство базовых аппаратных средств Структурировано с блоком fle и размерами кэшей, равными степени 2. Классы Buffered Позволяют указывать размер буфера. в конструкторе. Если ничего не предоставлено, они Используют значение по умолчанию, которое в большинстве JVM является степенью 2.

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

0
Adrian Krebs