Когда использовать массив, буфер или прямой буфер
Вопрос
При написании класса Matrix для использования с библиотеками OpenGL я столкнулся с вопросом, использовать ли массивы Java или стратегию Buffer для хранения данных (JOGL предлагает копирование с прямым буфером для операций Matrix). Чтобы проанализировать это, я написал небольшую программу тестирования производительности, которая сравнивает относительные скорости циклических и массовых операций в массивах против буферов против прямых буферов.
Я хотел бы поделиться своими результатами с вами здесь (поскольку я нахожу их довольно интересными). Пожалуйста, не стесняйтесь комментировать и / или указывать на любые ошибки.
Код можно посмотреть на http://pastebin.com/is7UaiMV.
Заметки
Массив циклического чтения реализован как A[i] = B[i], иначе оптимизатор JIT полностью удалит этот код. Фактическое var = A[i] кажется почти одинаковым.
В примере примера для размера массива 10000 очень вероятно, что оптимизатор JIT заменил доступ к зацикленному массиву реализацией, подобной System.arraycopy.
Не существует буфера массового получения-> буфер, поскольку Java реализует A.get(B) как B.put (A), поэтому результаты будут такими же, как и результаты массового размещения.
Заключение
Практически во всех ситуациях настоятельно рекомендуется использовать внутренние массивы Java. Мало того, что скорость ввода / вывода значительно выше, JIT также может выполнять намного лучшую оптимизацию конечного кода.
Буферы следует использовать только в том случае, если применимо следующее:
- Вам нужно обрабатывать большие объемы данных.
- Эти данные в основном или всегда массово обрабатываются.
Обратите внимание, что backened-buffer имеет Java Array, поддерживающий содержимое буфера. Рекомендуется выполнять операции с этим обратным буфером вместо цикла put / get.
Прямые буферы следует использовать, только если вы беспокоитесь об использовании памяти и никогда не обращаетесь к базовым данным. Они немного медленнее, чем непрямые буферы, намного медленнее при обращении к базовым данным, но используют меньше памяти. Кроме того, есть дополнительные издержки при преобразовании небайтовых данных (таких как массивы с плавающей запятой) в байты при использовании прямого буфера.
Для более подробной информации смотрите здесь:
- Почему только ByteBuffers полезны при использовании прямых буферов
- Внутренние издержки на NIO и что замедляет буферы
Пример результатов
Примечание: процент только для удобства чтения и не имеет никакого реального значения.
Использование массивов размером 16 с 10 000 000 итераций...
-- Array tests: -----------------------------------------
Loop-write array: 87.29 ms 11,52%
Arrays.fill: 64.51 ms 8,51%
Loop-read array: 42.11 ms 5,56%
System.arraycopy: 47.25 ms 6,23%
-- Buffer tests: ----------------------------------------
Loop-put buffer: 603.71 ms 79,65%
Index-put buffer: 536.05 ms 70,72%
Bulk-put array->buffer: 105.43 ms 13,91%
Bulk-put buffer->buffer: 99.09 ms 13,07%
Bulk-put bufferD->buffer: 80.38 ms 10,60%
Loop-get buffer: 505.77 ms 66,73%
Index-get buffer: 562.84 ms 74,26%
Bulk-get buffer->array: 137.86 ms 18,19%
-- Direct buffer tests: ---------------------------------
Loop-put bufferD: 570.69 ms 75,29%
Index-put bufferD: 562.76 ms 74,25%
Bulk-put array->bufferD: 712.16 ms 93,96%
Bulk-put buffer->bufferD: 83.53 ms 11,02%
Bulk-put bufferD->bufferD: 118.00 ms 15,57%
Loop-get bufferD: 528.62 ms 69,74%
Index-get bufferD: 560.36 ms 73,93%
Bulk-get bufferD->array: 757.95 ms 100,00%
Использование массивов размером 1000 с 100 000 итераций...
-- Array tests: -----------------------------------------
Loop-write array: 22.10 ms 6,21%
Arrays.fill: 10.37 ms 2,91%
Loop-read array: 81.12 ms 22,79%
System.arraycopy: 10.59 ms 2,97%
-- Buffer tests: ----------------------------------------
Loop-put buffer: 355.98 ms 100,00%
Index-put buffer: 353.80 ms 99,39%
Bulk-put array->buffer: 16.33 ms 4,59%
Bulk-put buffer->buffer: 5.40 ms 1,52%
Bulk-put bufferD->buffer: 4.95 ms 1,39%
Loop-get buffer: 299.95 ms 84,26%
Index-get buffer: 343.05 ms 96,37%
Bulk-get buffer->array: 15.94 ms 4,48%
-- Direct buffer tests: ---------------------------------
Loop-put bufferD: 355.11 ms 99,75%
Index-put bufferD: 348.63 ms 97,93%
Bulk-put array->bufferD: 190.86 ms 53,61%
Bulk-put buffer->bufferD: 5.60 ms 1,57%
Bulk-put bufferD->bufferD: 7.73 ms 2,17%
Loop-get bufferD: 344.10 ms 96,66%
Index-get bufferD: 333.03 ms 93,55%
Bulk-get bufferD->array: 190.12 ms 53,41%
Использование массивов размером 10 000 с 100 000 итераций...
-- Array tests: -----------------------------------------
Loop-write array: 156.02 ms 4,37%
Arrays.fill: 109.06 ms 3,06%
Loop-read array: 300.45 ms 8,42%
System.arraycopy: 147.36 ms 4,13%
-- Buffer tests: ----------------------------------------
Loop-put buffer: 3385.94 ms 94,89%
Index-put buffer: 3568.43 ms 100,00%
Bulk-put array->buffer: 159.40 ms 4,47%
Bulk-put buffer->buffer: 5.31 ms 0,15%
Bulk-put bufferD->buffer: 6.61 ms 0,19%
Loop-get buffer: 2907.21 ms 81,47%
Index-get buffer: 3413.56 ms 95,66%
Bulk-get buffer->array: 177.31 ms 4,97%
-- Direct buffer tests: ---------------------------------
Loop-put bufferD: 3319.25 ms 93,02%
Index-put bufferD: 3538.16 ms 99,15%
Bulk-put array->bufferD: 1849.45 ms 51,83%
Bulk-put buffer->bufferD: 5.60 ms 0,16%
Bulk-put bufferD->bufferD: 7.63 ms 0,21%
Loop-get bufferD: 3227.26 ms 90,44%
Index-get bufferD: 3413.94 ms 95,67%
Bulk-get bufferD->array: 1848.24 ms 51,79%
2 ответа
Прямые буферы не предназначены для ускорения доступа из кода Java. (Если бы это было возможно, было бы что-то не так с собственной реализацией массива JVM.)
Эти байтовые буферы предназначены для взаимодействия с другими компонентами, так как вы можете записать байтовый буфер в ByteChannel
и вы можете использовать прямые буферы в сочетании с собственным кодом, таким как библиотеки OpenGL, которые вы упомянули. Он предназначен для ускорения этих операций. Использование чипа графической карты для рендеринга может ускорить общую работу в большей степени, чем компенсация, возможно, более медленного доступа к буферу из кода Java.
Между прочим, если вы измеряете скорость доступа к байтовому буферу, особенно к прямым байтовым буферам, то стоит поменять порядок байтов на собственный порядок байтов, прежде чем получить FloatBuffer
Посмотреть:
FloatBuffer bufferD = ByteBuffer.allocateDirect(SIZE * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
Tldr:
Используйте прямые буферы только в том случае, если нам нужен эффективный высокоскоростной ввод / вывод.
Если нам нужны эффективные высокоскоростные операции без ввода / вывода, лучшим выбором будет массив по умолчанию.
Если нам нужно выполнить операции с буфером в массиве по умолчанию, и мы можем позволить себе работать медленно, используйте буфер на основе массива.
TSDR:
Ваши тесты не проверяли какие-либо операции ввода-вывода, и поэтому их вывод неверен.
Ваш вывод гласит (акцент не мой):
Прямые буферы следует использовать, только если вы беспокоитесь об использовании памяти и никогда не обращаетесь к базовым данным. Они немного медленнее, чем непрямые буферы, намного медленнее при обращении к базовым данным, но используют меньше памяти. Кроме того, есть дополнительные издержки при преобразовании небайтовых данных (таких как массивы с плавающей запятой) в байты при использовании прямого буфера.
Это явно неправильно. Прямые буферы предназначены для решения проблем со скоростью, а не из-за проблем с памятью. Прямые буферы следует использовать всякий раз, когда вам необходим высокопроизводительный доступ к вводу / выводу. Это включает в себя файловые / сетевые операции и т. Д. Это определенно быстрее при правильном использовании и на самом деле является самым быстрым, который Java API предоставляет "из коробки".
При выполнении файловых / сетевых операций возникают дополнительные издержки при преобразовании небайтовых данных в байты. Это верно для всего, а не только для прямых буферов.
Ваш вывод также гласит:
Обратите внимание, что backened-buffer имеет Java Array, поддерживающий содержимое буфера. Рекомендуется выполнять операции с этим обратным буфером вместо цикла put/get.
Это правда, но вы упускаете весь смысл буферов на основе массива. Буферы на основе массива - это рисунок фасада поверх массивов. Буферы, поддерживаемые массивом, никогда не будут быстрее самих массивов, поскольку внутри они должны использовать массив.
Как таковые, они существуют для удобства, а не для скорости. Другими словами, если вам нужна скорость, рекомендуется выбирать массив вместо array-фасад. Если вам нужно удобство / удобочитаемость, рекомендуется выбирать массив-фасад вместо массива для буферных операций над массивом.
Также прочитайте: