Как вы определяете идеальный размер буфера при использовании 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, чтобы сделать сообщение меньше
10 ответов
Оптимальный размер буфера зависит от нескольких факторов: размер блока файловой системы, размер кэша ЦП и задержка кэша.
Большинство файловых систем сконфигурировано для использования блоков размером 4096 или 8192. Теоретически, если вы конфигурируете размер буфера так, что вы читаете на несколько байтов больше, чем дисковый блок, операции с файловой системой могут быть крайне неэффективными (т.е. если вы сконфигурировал ваш буфер для чтения 4100 байт за раз, каждое чтение потребовало бы 2 блока чтения файловой системой). Если блоки уже находятся в кеше, вы платите цену RAM -> L3/L2 латентность кеша. Если вам не повезло, а блоки еще не находятся в кеше, вы также платите за задержку диска -> ОЗУ.
Вот почему вы видите большинство буферов, размер которых равен степени 2 и, как правило, больше (или равен) размеру блока диска. Это означает, что одно из ваших потоковых чтений может привести к нескольким дисковым чтениям блоков - но эти чтения всегда будут использовать полный блок - без потраченных чтений.
Теперь, это типично смещено в типичном сценарии потоковой передачи, потому что блок, который читается с диска, все еще будет в памяти, когда вы нажмете следующее чтение (в конце концов, мы делаем последовательное чтение здесь) - так что вы заводите при следующем чтении платят цену задержки ОЗУ -> L3 / L2, но не задержку диска -> ОЗУ. С точки зрения порядка величины задержка диска-> ОЗУ настолько медленная, что значительно перекрывает любую другую задержку, с которой вы можете иметь дело.
Итак, я подозреваю, что если вы запустили тест с разными размерами кэша (сам этого не сделал), вы, вероятно, обнаружите большое влияние размера кэша вплоть до размера блока файловой системы. Кроме того, я подозреваю, что все выровняется довольно быстро.
Здесь существует масса условий и исключений - сложности системы на самом деле весьма ошеломляют (просто получить контроль над передачей кэш-памяти L3 -> L2 невероятно сложно, и она меняется с каждым типом процессора).
Это приводит к ответу "реального мира": если ваше приложение на 99%, установите размер кэша равным 8192 и двигайтесь дальше (еще лучше, выберите инкапсуляцию вместо производительности и используйте BufferedInputStream, чтобы скрыть детали). Если вы находитесь в 1% приложений, которые сильно зависят от пропускной способности диска, создайте свою реализацию так, чтобы вы могли поменять различные стратегии взаимодействия с диском и предоставить ручки и наборы, чтобы позволить пользователям тестировать и оптимизировать (или придумать некоторые самооптимизирующаяся система).
Да, это, вероятно, зависит от разных вещей - но я сомневаюсь, что это будет иметь большое значение. Я имею тенденцию выбирать 16K или 32K в качестве хорошего баланса между использованием памяти и производительностью.
Обратите внимание, что в коде должен быть блок try/finally, чтобы убедиться, что поток закрыт, даже если выдается исключение.
В большинстве случаев это не так важно. Просто выберите хороший размер, например 4K или 16K, и придерживайтесь его. Если вы уверены, что это узкое место в вашем приложении, то вам следует начать профилирование, чтобы найти оптимальный размер буфера. Если вы выберете слишком маленький размер, вы будете тратить время на дополнительные операции ввода-вывода и дополнительные вызовы функций. Если вы выберете слишком большой размер, вы начнете видеть много пропусков кэша, которые действительно замедлят вас. Не используйте буфер больше, чем ваш размер кэша L2.
Чтение файлов с использованием JavaCIO FileChannel и MappedByteBuffer, скорее всего, приведет к решению, которое будет намного быстрее, чем любое решение, включающее FileInputStream. В основном, карты памяти большие файлы, и использовать прямые буферы для маленьких.
Вы можете использовать BufferedStreams/reader, а затем использовать их размеры буфера.
Я полагаю, что BufferedXStreams использует 8192 в качестве размера буфера, но, как сказал Овидиу, вам, вероятно, следует выполнить тест для целого ряда параметров. Это действительно будет зависеть от файловой системы и конфигурации диска относительно того, каковы лучшие размеры.
В идеальном случае у нас должно быть достаточно памяти для чтения файла за одну операцию чтения. Это было бы лучше всего, потому что мы позволяем системе управлять файловой системой, распределительными блоками и жесткими дисками по желанию. На практике вам повезло заранее знать размеры файлов, просто используйте средний размер файла, округленный до 4 КБ (единица выделения по умолчанию в NTFS). И самое главное: создайте тест для тестирования нескольких вариантов.
В источнике BufferedInputStream вы найдете: private static int DEFAULT_BUFFER_SIZE = 8192;
Так что вы можете использовать это значение по умолчанию.
Но если вы сможете узнать больше информации, вы получите более ценные ответы.
Например, ваш adsl может иметь буфер 1454 байта, потому что полезная нагрузка TCP/IP. Для дисков вы можете использовать значение, соответствующее размеру блока вашего диска.
Как уже упоминалось в других ответах, используйте BufferedInputStreams.
После этого, я думаю, размер буфера не имеет большого значения. Либо программа связана с вводом / выводом, и увеличение размера буфера по сравнению с BIS по умолчанию не окажет большого влияния на производительность.
Или программа связана с ЦП внутри MessageDigest.update(), и большая часть времени не тратится на код приложения, поэтому его настройка не поможет.
(Хм... с несколькими ядрами, потоки могут помочь.)
1024 подходит для широкого спектра обстоятельств, хотя на практике вы можете увидеть лучшую производительность с большим или меньшим размером буфера.
Это будет зависеть от ряда факторов, включая размер блока файловой системы и аппаратное обеспечение процессора.
Также обычно выбирают степень 2 для размера буфера, так как большая часть базового оборудования структурирована с блочным блоком и размерами кэша, которые являются степенью 2. Классы Buffered позволяют указывать размер буфера в конструкторе. Если ничего не указано, они используют значение по умолчанию, которое в большинстве JVM является степенью 2.
Независимо от того, какой размер буфера вы выберете, наибольшее увеличение производительности вы увидите при переходе от небуферизованного к буферизованному доступу к файлам. Регулировка размера буфера может немного улучшить производительность, но если вы не используете очень маленький или очень большой размер буфера, это вряд ли окажет значительное влияние.
Microsoft считает, что 64k является оптимальным