java - 为什么直接内存 'array' 的清除速度比普通的 Java 数组要慢?

标签 java performance jmh

我已经建立了一个 JMH 基准测试来衡量使用 null、来自 null 数组的 System.arraycopy、将 DirectByteBuffer 归零或将 Arrays.fill 更快的方法试图回答这个问题的不安全内存块question 让我们先把直接分配的内存置零的情况放在一边,讨论一下我的基准测试结果。

这里是 JMH 基准代码片段 ( full code available via a gist ),包括 @apangin 在原始帖子中建议的 unsafe.setMemory 情况,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 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.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?

最佳答案

Why unsafeArraySetMemory is a bit but slower than unsafeArrayManualLoop?

我的猜测是,它对于设置多个长整型没有很好的优化。它必须检查你是否有一些东西,而不是 8 的倍数。

Why directByteBuffer is by an order of magnitude slower than others?

数量级约为 10 倍,速度慢约 2.5 倍。它必须对每次访问进行边界检查并更新字段而不是局部变量。

注意:我发现 JVM 并不总是使用 Unsafe 循环展开代码。您可以尝试自己这样做,看看是否有帮助。

注意: native 代码可以使用 XMM 128 位指令,并且越来越多地使用它,这就是复制速度如此之快的原因。 Java 10 中可能会提供对 XMM 指令的访问。

关于java - 为什么直接内存 'array' 的清除速度比普通的 Java 数组要慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42299184/

相关文章:

JavaFX。将多个带有图像的标签添加到 Pane

Python:最佳字典实现

scala - 如何将 JMH 与 sbt 一起用于 Scala 基准测试?

java - 从表中获取最新的列,并按 HQL 中的另一列进行分组

java - Android - 将指纹复制到其他设备

java - 尝试使用带有 'menu' 的 For 循环

javascript - 如何检查脚本依赖性?

mysql - 当连接到一个非常小/空的表时,为什么尽管我使用的是 "LIMIT",MySQL 还是进行了全面扫描?

java - 直接 ByteBuffer 相对与绝对读取性能