What is the fastest way to set an arbitrary range of elements in a Java array to null?

泄露秘密 提交于 2019-12-05 01:11:46

If I got it right, you need to nullify an array, or a sub-range of an array containing references to objects to make them eligible for GC. And you have a regular Java array, which stores data on-heap.

Answering your question, System.arrayCopy is the fastest way to null a sub-range of an array. It is worse memory-wise than Arrays.fill though, since you would have to allocate twice as much memory to hold references at worst case for an array of nulls you can copy from. Though if you need to fully null an array, even faster would be just to create a new empty array (e.g. new Object[desiredLength]) and replace the one you want to nullify with it.

Unsafe, DirectByteBuffer, DirectLongBuffer implementations doesn't provide any performance gain in a naive straight-forward implementation (i.e. if you just replace the Array with DirectByteBuffer or Unsafe). They are slower then bulk System.arrayCopy as well. Since those implementations have nothing to do with Java Array, they're out of scope of your question anyway.

Here's my JMH benchmark (full benchmark code available via gist) snippet for those including unsafe.setMemory case as per @apangin comment; and including ByteBuffer.put(long[] src, int srcOffset, int longCount) as per @jan-chaefer; and an equivalent of Arrays.fill loop as per @scott-carey to check if Arrays.fill could be an intrinsic in 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);
}

Here's what I got (Java 1.8, JMH 1.13, Core i3-6100U 2.30 GHz, 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. Speaking of ByteBuffer and Unsafe - their main benefits in your case is that they store data off-heap, and you can implement your own memory deallocation alghorithm which would siut your data-structure better than regular GC. So you won't need to nullify them, and could compact memory as you please. Most likely the efforts won't worth much, since it would be much easier to get a less performant and more error-prone code then you have now.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!