Почему "массив" прямой памяти медленнее очищается, чем обычный массив Java?
Я установил тест JMH, чтобы измерить, что будет быстрее Arrays.fill
с нулем, System.arraycopy
из нулевого массива, обнуление DirectByteBuffer или обнуление unsafe
Блок памяти пытается ответить на этот вопрос. Давайте отложим в сторону, что обнуление непосредственно выделенной памяти - редкий случай, и обсудим результаты моего теста.
Вот фрагмент кода JMH ( полный код доступен через gist), включая unsafe.setMemory
случай, как предложено @apangin в оригинальном сообщении, byteBuffer.put(byte[], offset, length)
а также longBuffer.put(long[], offset, length)
как предложено @jan-schaefer:
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void arrayFill() {
Arrays.fill(objectHolderForFill, null);
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void arrayCopy() {
System.arraycopy(nullsArray, 0, objectHolderForArrayCopy, 0, objectHolderForArrayCopy.length);
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void directByteBufferManualLoop() {
while (referenceHolderByteBuffer.hasRemaining()) {
referenceHolderByteBuffer.putLong(0);
}
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void directByteBufferBatch() {
referenceHolderByteBuffer.put(nullBytes, 0, nullBytes.length);
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void directLongBufferManualLoop() {
while (referenceHolderLongBuffer.hasRemaining()) {
referenceHolderLongBuffer.put(0L);
}
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void directLongBufferBatch() {
referenceHolderLongBuffer.put(nullLongs, 0, nullLongs.length);
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void unsafeArrayManualLoop() {
long addr = referenceHolderUnsafe;
long pos = 0;
for (int i = 0; i < size; i++) {
unsafe.putLong(addr + pos, 0L);
pos += 1 << 3;
}
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void unsafeArraySetMemory() {
unsafe.setMemory(referenceHolderUnsafe, size*8, (byte) 0);
}
Вот что я получил (Java 1.8, JMH 1.13, Core i3-6100U 2,30 ГГц, Win10):
100 elements
Benchmark Mode Cnt Score Error Units
ArrayNullFillBench.arrayCopy sample 5234029 39,518 ± 0,991 ns/op
ArrayNullFillBench.directByteBufferBatch sample 6271334 43,646 ± 1,523 ns/op
ArrayNullFillBench.directLongBufferBatch sample 4615974 45,252 ± 2,352 ns/op
ArrayNullFillBench.arrayFill sample 4745406 76,997 ± 3,547 ns/op
ArrayNullFillBench.unsafeArrayManualLoop sample 5980381 78,811 ± 2,870 ns/op
ArrayNullFillBench.unsafeArraySetMemory sample 5985884 85,062 ± 2,096 ns/op
ArrayNullFillBench.directLongBufferManualLoop sample 4697023 116,242 ± 2,579 ns/op WOW
ArrayNullFillBench.directByteBufferManualLoop sample 7504629 208,440 ± 10,651 ns/op WOW
I skipped all the loop implementations (except arrayFill for scale) from further tests
1000 elements
Benchmark Mode Cnt Score Error Units
ArrayNullFillBench.arrayCopy sample 6780681 184,516 ± 14,036 ns/op
ArrayNullFillBench.directLongBufferBatch sample 4018778 293,325 ± 4,074 ns/op
ArrayNullFillBench.directByteBufferBatch sample 4063969 313,171 ± 4,861 ns/op
ArrayNullFillBench.arrayFill sample 6862928 518,886 ± 6,372 ns/op
10000 elements
Benchmark Mode Cnt Score Error Units
ArrayNullFillBench.arrayCopy sample 2551851 2024,543 ± 12,533 ns/op
ArrayNullFillBench.directLongBufferBatch sample 2958517 4469,210 ± 10,376 ns/op
ArrayNullFillBench.directByteBufferBatch sample 2892258 4526,945 ± 33,443 ns/op
ArrayNullFillBench.arrayFill sample 5689507 5028,592 ± 9,074 ns/op
Не могли бы вы уточнить следующие вопросы:
1. Why `unsafeArraySetMemory` is a bit but slower than `unsafeArrayManualLoop`?
2. Why directByteBuffer is 2.5X-5X slower than others?
2 ответа
Почему unsafeArraySetMemory немного, но медленнее, чем unsafeArrayManualLoop?
Я думаю, что он не так хорошо оптимизирован для установки ровно нескольких длинных. Он должен проверить, есть ли у вас что-то, не очень кратное 8.
Почему directByteBuffer на порядок медленнее, чем другие?
Порядок величины будет примерно в 10 раз, это примерно в 2,5 раза медленнее. Он должен ограничивать проверку каждого доступа и обновлять поле вместо локальной переменной.
ПРИМЕЧАНИЕ. Я обнаружил, что JVM не всегда зацикливает код развертывания с помощью Unsafe. Вы можете попробовать сделать это самостоятельно, чтобы увидеть, поможет ли это.
ПРИМЕЧАНИЕ. Нативный код может использовать 128-битные инструкции XMM и использует его все чаще, поэтому копирование может быть таким быстрым. Доступ к инструкции XMM может прийти в Java 10.
Сравнение немного несправедливо. Вы используете одну операцию при использовании Array.fill
а также System.arraycopy
, но вы используете цикл и несколько вызовов putLong
в DirectByteBuffer
дело. Если вы посмотрите на реализацию putLong
вы увидите, что там много чего происходит, например, проверка доступности. Вы должны попытаться использовать пакетную операцию, как put(long[] src, int srcOffset, int longCount)
и посмотрим, что получится.