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

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

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

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

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

9 127

9 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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