Когда использовать массив, буфер или прямой буфер

Вопрос

При написании класса 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.

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

Для более подробной информации смотрите здесь:

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

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

Использование массивов размером 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-фасад. Если вам нужно удобство / удобочитаемость, рекомендуется выбирать массив-фасад вместо массива для буферных операций над массивом.

Также прочитайте:

Другие вопросы по тегам