Я знаю, что могу просто перебирать от start
до end
и очищать эти ячейки, но мне было интересно, возможно ли это каким-либо другим способом (возможно, с использованием JNI-ed System.arrayCopy
)?
Каков самый быстрый способ установить произвольный диапазон элементов в массиве Java равным нулю?
Ответ 1
Если я правильно понял, вам нужно свернуть массив или поддиапазон массива, содержащий ссылки на объекты, чтобы сделать их доступными для GC. И у вас есть обычный массив Java, который хранит данные в куче.
Отвечая на ваш вопрос, System.arrayCopy
- это самый быстрый способ обнулить поддиапазон массива. Это хуже по памяти, чем Arrays.fill
, хотя вам придется выделять в два раза больше памяти для хранения ссылок в худшем случае для массива нулей, с которого вы можете скопировать. Хотя, если вам нужно полностью исключить массив, еще быстрее будет просто создать новый пустой массив (например, new Object[desiredLength]
) и заменить тот, который вы хотите свести с него.
Unsafe
, DirectByteBuffer
, DirectLongBuffer
реализация не обеспечивает прироста производительности в наивной прямолинейной реализации (т.е. если вы просто замените Array
на DirectByteBuffer
или Unsafe
). Они медленнее, чем объем System.arrayCopy
. Поскольку эти реализации не имеют ничего общего с Java Array
, они все равно выходят за рамки вашего вопроса.
Здесь мой тест JMH (полный тестовый код доступен через gist) фрагмент для тех, кто включает unsafe.setMemory
случай в соответствии с комментарием @apangin; и включая ByteBuffer.put(long[] src, int srcOffset, int longCount)
согласно @jan-chaefer; и эквивалент цикла Arrays.fill
в соответствии с @scott-carey, чтобы проверить, может ли Arrays.fill
быть встроенным в JDK 8.
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void arrayFill() {
Arrays.fill(objectHolderForFill, null);
}
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public void arrayFillManualLoop() {
for (int i = 0, len = objectHolderForFill.length; i < len; i++) {
objectHolderForLoop[i] = 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.arrayFillManualLoop sample 5549216 78,677 ± 13,013 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 from further tests
** - except arrayFill and arrayFillManualLoop for scale
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.arrayFillManualLoop sample 6270397 543,801 ± 20,325 ns/op
ArrayNullFillBench.arrayFill sample 6590416 548,250 ± 13,475 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 2578580 5532,063 ± 20,705 ns/op
ArrayNullFillBench.arrayFillManualLoop sample 2562569 5550,195 ± 40,666 ns/op
P.S.
Говоря о ByteBuffer
и Unsafe
, их основные преимущества в вашем случае состоят в том, что они хранят данные с кучи, и вы можете реализовать свой собственный алгоритм освобождения памяти, который бы лучше соответствовал вашей структуре данных, чем обычный GC. Таким образом, вам не нужно будет сворачивать их, и вы можете компактно хранить память. Скорее всего, усилия не будут стоить многого, так как было бы намного проще получить менее эффективный и более подверженный ошибкам код, чем сейчас.