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

156

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

ARKBAN
источник

Ответы:

213

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

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

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

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

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

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

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

Кевин Дэй
источник
3
Я выполнил некоторые банчмаркинг на мобильном телефоне (Nexus 5X) для своего приложения для Android как для небольших файлов (3,5 МБ), так и для больших файлов (175 МБ). И обнаружил, что золотой размер будет байтом [] 524288 длин. Что ж, вы можете выиграть 10-20 мс, если переключаетесь между маленьким буфером 4 КБ и большим буфером 524 КБ в зависимости от размера файла, но это того не стоит. Таким образом, 524 Кб был лучшим вариантом в моем случае.
Кирилл Кармазин
19

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

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

Джон Скит
источник
Я отредактировал пост о try..catch. В моем реальном коде у меня есть один, но я упустил его, чтобы сделать сообщение короче.
АРКБАН
1
если мы хотим определить фиксированный размер для него, какой размер лучше? 4к, 16к или 32к?
BattleTested
2
@MohammadrezaPanahi: Пожалуйста, не используйте комментарии для пользователей барсука. Вы подождали менее часа до второго комментария. Пожалуйста, помните, что пользователи могут легко спать, или на собраниях, или в основном заняты другими делами и не имеют никаких обязательств отвечать на комментарии. Но чтобы ответить на ваш вопрос: это полностью зависит от контекста. Если вы работаете в системе с очень ограниченным объемом памяти, вам, вероятно, нужен небольшой буфер. Если вы работаете в большой системе, использование большего буфера уменьшит количество вызовов чтения. Ответ Кевина Дея очень хорош.
Джон Скит
7

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

Адам Розенфилд
источник
4

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

Овидиу Пакурар
источник
Вы имеете в виду, что лучший размер буфера для чтения и записи в файл - 4 КБ?
BattleTested
4

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

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

Джон Гарднер
источник
4

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

Александр
источник
4

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

GoForce5500
источник
1

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

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

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

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

Maglob
источник
0

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

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

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

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

Адриан Кребс
источник