Каков самый быстрый способ установить произвольный диапазон элементов в массиве Java равным нулю?

Я знаю, что могу просто перебирать от start до end и очищать эти ячейки, но мне было интересно, возможно ли это каким-либо другим способом (возможно, с использованием JNI-ed System.arrayCopy)?

Ответ 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. Таким образом, вам не нужно будет сворачивать их, и вы можете компактно хранить память. Скорее всего, усилия не будут стоить многого, так как было бы намного проще получить менее эффективный и более подверженный ошибкам код, чем сейчас.