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

StackOverflow https://stackoverflow.com/questions/236861

Вопрос

У меня есть метод, который создает 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, так что это может быть просто вызов API Java, о котором я не знаю.)

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

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

Это было полезно?

Решение

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

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

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

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

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

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

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

Другие советы

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top