Как определить идеальный размер буфера при использовании 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();

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

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

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

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

Ответы

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

Ответ 5

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

Ответ 6

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

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

Ответ 7

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

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

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

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

Ответ 8

Сделайте буфер достаточно большим, чтобы большая часть файлов читалась одним выстрелом. Не забудьте повторно использовать один и тот же буфер и тот же MessageDigest для чтения разных файлов.

Не связан с вопросом: ознакомьтесь с условными обозначениями кода Sun, особенно с интервалом вокруг parens и использованием избыточных фигурных скобок. Избегайте оператора = в инструкции while или if

Ответ 9

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

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

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

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

Ответ 10

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