ByteBuffer.allocate() против ByteBuffer.allocateDirect()
К allocate()
или allocateDirect()
, вот в чем вопрос.
В течение нескольких лет я просто придерживался мысли, что с DirectByteBuffer
Это прямое отображение памяти на уровне ОС, которое будет выполняться быстрее с вызовами get/put, чем HeapByteBuffer
s. Я никогда не интересовался точными подробностями ситуации до сих пор. Я хочу знать, какой из двух типов ByteBuffer
быстрее и на каких условиях.
4 ответа
Рон Хитчс в своей превосходной книге " Java NIO", кажется, предлагает то, что я думаю, может быть хорошим ответом на ваш вопрос:
Операционные системы выполняют операции ввода-вывода в областях памяти. Эти области памяти, с точки зрения операционной системы, представляют собой непрерывные последовательности байтов. Поэтому неудивительно, что только байтовые буферы могут участвовать в операциях ввода-вывода. Также напомним, что операционная система будет напрямую обращаться к адресному пространству процесса, в данном случае к процессу JVM, для передачи данных. Это означает, что области памяти, которые являются объектами операций ввода-вывода, должны представлять собой непрерывные последовательности байтов. В JVM массив байтов не может храниться непрерывно в памяти, или сборщик мусора может перемещать его в любое время. Массивы - это объекты в Java, и способ хранения данных внутри этого объекта может варьироваться от одной реализации JVM к другой.
По этой причине было введено понятие прямого буфера. Прямые буферы предназначены для взаимодействия с каналами и собственными процедурами ввода / вывода. Они делают все возможное, чтобы сохранить байтовые элементы в области памяти, которую канал может использовать для прямого или необработанного доступа, используя собственный код, чтобы сообщить операционной системе о необходимости опустошения или заполнения области памяти.
Прямые байтовые буферы обычно являются лучшим выбором для операций ввода / вывода. По своей конструкции они поддерживают наиболее эффективный механизм ввода / вывода, доступный для JVM. Непрямые байтовые буферы могут передаваться на каналы, но это может повлечь за собой снижение производительности. Обычно непрямой буфер не может быть целью собственной операции ввода-вывода. Если вы передаете непрямой объект ByteBuffer в канал для записи, канал может неявно выполнять следующие действия при каждом вызове:
- Создайте временный прямой объект ByteBuffer.
- Скопируйте содержимое непрямого буфера во временный буфер.
- Выполните операцию ввода-вывода низкого уровня, используя временный буфер.
- Временный буферный объект выходит из области видимости и в конечном итоге собирается мусором.
Это может привести к копированию буфера и оттоку объекта при каждом вводе / выводе, чего мы и хотели бы избежать. Однако, в зависимости от реализации, все может быть не так плохо. Среда выполнения, скорее всего, будет кешировать и повторно использовать прямые буферы или выполнять другие хитрые приемы для повышения пропускной способности. Если вы просто создаете буфер для одноразового использования, разница не будет значительной. С другой стороны, если вы будете использовать буфер многократно в сценарии с высокой производительностью, вам лучше выделить прямые буферы и использовать их повторно.
Прямые буферы оптимальны для ввода / вывода, но их создание может оказаться более дорогим, чем непрямые байтовые буферы. Память, используемая прямыми буферами, выделяется путем обращения к собственному, специфичному для операционной системы коду, минуя стандартную кучу JVM. Настройка и удаление прямых буферов может быть значительно дороже, чем резидентные буферы кучи, в зависимости от операционной системы хоста и реализации JVM. Области памяти прямых буферов не подлежат сбору мусора, поскольку они находятся за пределами стандартной кучи JVM.
Компромиссы производительности использования прямых и непрямых буферов могут широко варьироваться в зависимости от JVM, операционной системы и дизайна кода. Выделив память вне кучи, вы можете подвергнуть свое приложение дополнительным силам, о которых JVM не знает. При вводе в действие дополнительных движущихся частей убедитесь, что вы достигаете желаемого эффекта. Я рекомендую старое программное обеспечение: сначала сделай так, а потом быстро. Не беспокойтесь об оптимизации заранее; сосредоточиться в первую очередь на правильности. Реализация JVM может быть в состоянии выполнить кэширование буфера или другие оптимизации, которые обеспечат необходимую вам производительность без большого количества ненужных усилий с вашей стороны.
Нет оснований ожидать, что прямые буферы будут быстрее для доступа внутри jvm. Их преимущество приходит, когда вы передаете их нативному коду - например, коду за каналами всех видов.
поскольку DirectByteBuffers - это прямое отображение памяти на уровне ОС
Это не так. Они представляют собой обычную память процесса приложения, но не подлежат перемещению во время Java GC, что значительно упрощает работу на уровне JNI. То, что вы описываете, относится к MappedByteBuffer
,
что он будет работать быстрее с вызовами get/put
Вывод не вытекает из предпосылки; предпосылка ложная; и заключение также ложно. Они быстрее, когда вы попадаете в слой JNI, и если вы читаете и пишете с того же DirectByteBuffer
они намного быстрее, потому что данные никогда не должны пересекать границу JNI вообще.
Лучше всего делать свои собственные измерения. Быстрый ответ, кажется, что отправка от allocateDirect()
буфер занимает от 25% до 75% меньше времени, чем allocate()
вариант (тестируется как копирование файла в /dev/null), в зависимости от размера, но само выделение может быть значительно медленнее (даже в 100 раз).
Источники: