将Java数组中任意范围的元素设置为null的最快方法是什么?

时间:2017-02-09 06:49:58

标签: java performance java-native-interface arraycopy

我知道我可以简单地从start迭代到end并清除那些单元格,但我想知道是否有可能以更快的方式(可能使用JNI编辑System.arrayCopy)?

1 个答案:

答案 0 :(得分:17)

如果我做对了,你需要使一个数组或一个包含对象引用的数组的子范围无效,以使它们符合GC的条件。而且你有一个常规的Java数组,它可以在堆上存储数据。

回答你的问题,System.arrayCopy是取消数组子范围的最快方法。然而,它比Arrays.fill在内存方面更糟糕,因为在最坏的情况下,你必须分配两倍的内存来保存引用,你可以复制一个空数组。虽然如果你需要完全使数组为空,那么更快就是创建一个新的空数组(例如new Object[desiredLength])并用它取代你想要的数组。

UnsafeDirectByteBufferDirectLongBuffer实现在天真的直接实现中没有提供任何性能提升(即,如果您只是将Array替换为DirectByteBuffer UnsafeSystem.arrayCopy)。它们比批量Array慢。由于这些实现与Java unsafe.setMemory无关,因此无论如何它们都超出了您的问题范围。

这是我的JMH基准(可用的完整基准代码via gist)代码段,包括ByteBuffer.put(long[] src, int srcOffset, int longCount)个案例,根据@apangin评论;并根据@ jan-chaefer包括Arrays.fill;以及@ scott-carey的等效Arrays.fill循环来检查@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); } 是否可能是JDK 8中的内在函数。

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

这是我得到的(Java 1.8,JMH 1.13,Core i3-6100U 2.30 GHz,Win10):

ByteBuffer

P.S。 说到UnsafePath path = Paths.get(new File("User_privkey.pem").getAbsolutePath()); Path certPath = Paths.get(new File("User.pem").getAbsolutePath()); try { // Used to read User_privkey.pem file to get private key PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Files.readAllBytes(path)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateKey = keyFactory.generatePrivate(spec); // Used to read user certificate CertificateFactory factory = CertificateFactory.getInstance("X.509"); Certificate cert = factory.generateCertificate(Files.newInputStream(certPath, null)); KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType()); // add it to the keystore ks.setKeyEntry("MyPKCSEntry", privateKey, "Temp".toCharArray(), new Certificate[] { cert }); File file = new File("CERTIFICATE_CUSTOMPATH"); OutputStream out = new FileOutputStream(file); ks.store(out, "Temp".toCharArray()); out.close(); } catch (Exception e) { System.out.println("Exception got caught" + e.getMessage()); } - 它们的主要好处是它们可以在堆外存储数据,并且您可以实现自己的内存释放算法,这样可以比常规GC更好地处理数据结构。所以你不需要取消它们,并且可以随心所欲地压缩内存。很可能这些努力都不值得,因为现在你可以更容易地获得性能较差且更容易出错的代码。