Когда использовать Array, Buffer или direct Buffer

Вопрос

При написании класса Matrix для использования с библиотеками OpenGL я столкнулся с вопросом о том, использовать ли Java-массивы или стратегию Buffer для хранения данных (JOGL предлагает прямую копию буфера для операций Matrix). Чтобы проанализировать это, я написал небольшую программу тестирования производительности, которая сравнивает относительные скорости циклов и объемных операций в массивах против буферов против прямых буферов.

Я хотел бы поделиться с вами своими результатами (как я нахожу их довольно интересными). Пожалуйста, не стесняйтесь комментировать и/или указывать на любые ошибки.
Код можно просмотреть на pastebin.com/is7UaiMV.

Примечания

  • Массив Loop-read реализован как A [i] = B [i], так как в противном случае оптимизатор JIT полностью удалит этот код. Фактический var = A [i] кажется почти таким же.

  • В образце результата для размера массива 10 000 очень вероятно, что оптимизатор JIT заменил доступ к петлевому массиву с реализацией System.arraycopy.

  • Нет буфера bulk-get buffer- > , поскольку Java реализует A.get(B) как B.put(A), поэтому результаты будут такими же, как результаты массового ввода.

Заключение

В почти всех ситуациях настоятельно рекомендуется использовать внутренние массивы Java. Мало того, что скорость put/get значительно выше, JIT также способен выполнять намного лучшие оптимизации в конечном коде.

Буферы должны использоваться только, если и применяется следующее:

  • Вам нужно обработать большие суммы данных.
  • Эти данные в основном или всегда массовые обработки.

Обратите внимание, что в буфере с резервной копией имеется массив Java, поддерживающий содержимое буфера. Рекомендуется выполнять операции над этим обратным буфером вместо цикла put/get.

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

Подробнее см. здесь:

Примеры результатов

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

Использование массивов размера 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%

Использование массивов размером 10000 с 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%

Ответ 1

Прямые буферы не предназначены для ускорения доступа с Java-кода. (Если бы это было возможно, что-то не так с реализацией собственного массива JVM.)

Эти байтовые буферы предназначены для взаимодействия с другими компонентами, так как вы можете записать буфер байта в ByteChannel, и вы можете использовать прямые буферы в в сочетании с собственным кодом, например, с упомянутыми вами библиотеками OpenGL. Его целью было ускорить эту операцию. Использование чипа графических карт для рендеринга может ускорить общую операцию до степени, более чем компенсирующей возможно более медленный доступ к буфере из кода Java.

Кстати, если вы измеряете скорость доступа в буфер байта, особенно в буферах прямого байта, стоит изменить порядок байтов на собственный байтовый порядок, прежде чем приобретать FloatBuffer:

FloatBuffer bufferD = ByteBuffer.allocateDirect(SIZE * 4)
                                .order(ByteOrder.nativeOrder())
                                .asFloatBuffer();

Ответ 2

TL;DR:

Используйте только прямые буферы только, если нам нужно делать эффективные высокоскоростные I/O.

Если нам нужны эффективные высокоскоростные операции не-I/O, массив по умолчанию - лучший выбор.

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

TSDR:

Ваши тесты не тестировали какие-либо операции ввода-вывода, и поэтому вывод не соответствует действительности.

В вашем заключении говорится (не мой):

Прямые буферы должны использоваться только, если вы беспокоитесь о памяти использование и никогда не обращаться к базовым данным. Они немного медленнее чем непрямые буферы, намного медленнее, если базовые данные доступ к ним, но использование меньше памяти. Кроме того, есть дополнительные накладные расходы при преобразовании небайтовых данных (таких как float-массивы) в байты, когда используя прямой буфер.

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

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

В вашем заключении также говорится:

Обратите внимание, что буфер с резервной копией имеет Java-массив, поддерживающий содержимое буфер. Рекомендуется выполнять операции над этим обратным буфером вместо looping put/get.

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

Таким образом, они доступны для удобства, а не для скорости. Другими словами, если вам нужна скорость, рекомендуется выбрать массив поверх массива-фасада. Если вам нужна удобство/удобочитаемость, рекомендуется выбрать массив-фасад над массивом для операций с буфером в массиве.

Также читайте: