【问题标题】:Direct ByteBuffer relative vs absolute read performance直接 ByteBuffer 相对与绝对读取性能
【发布时间】:2017-01-12 04:46:38
【问题描述】:

在测试直接 java.nio.ByteBuffer 的读取性能时,我注意到绝对读取平均比相对读取快 2 倍。此外,如果我比较相对读取和绝对读取的源代码,除了相对读取维护和内部计数器之外,代码几乎相同。我想知道为什么我看到速度差异如此之大?

以下是我的 JMH 基准测试的源代码:

public class DirectByteBufferReadBenchmark {

    private static final int OBJ_SIZE = 8 + 4 + 1;
    private static final int NUM_ELEM = 10_000_000;

    @State(Scope.Benchmark)
    public static class Data {

        private ByteBuffer directByteBuffer;

        @Setup
        public void setup() {
            directByteBuffer = ByteBuffer.allocateDirect(OBJ_SIZE * NUM_ELEM);
            for (int i = 0; i < NUM_ELEM; i++) {
                directByteBuffer.putLong(i);
                directByteBuffer.putInt(i);
                directByteBuffer.put((byte) (i & 1));
            }
        }
    }



    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public long testReadAbsolute(Data d) throws InterruptedException {
        long val = 0l;
        for (int i = 0; i < NUM_ELEM; i++) {
            int index = OBJ_SIZE * i;
            val += d.directByteBuffer.getLong(index);
            d.directByteBuffer.getInt(index + 8);
            d.directByteBuffer.get(index + 12);
        }
        return val;
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    @OutputTimeUnit(TimeUnit.SECONDS)
    public long testReadRelative(Data d) throws InterruptedException {
        d.directByteBuffer.rewind();

        long val = 0l;
        for (int i = 0; i < NUM_ELEM; i++) {
            val += d.directByteBuffer.getLong();
            d.directByteBuffer.getInt();
            d.directByteBuffer.get();
        }

        return val;
    }

    public static void main(String[] args) throws Exception {
        Options opt = new OptionsBuilder()
            .include(DirectByteBufferReadBenchmark.class.getSimpleName())
            .warmupIterations(5)
            .measurementIterations(5)
            .forks(3)
            .threads(1)
            .build();

        new Runner(opt).run();
    }
}

这些是我的基准测试的结果:

Benchmark                                        Mode  Cnt   Score   Error  Units
DirectByteBufferReadBenchmark.testReadAbsolute  thrpt   15  88.605 ± 9.276  ops/s
DirectByteBufferReadBenchmark.testReadRelative  thrpt   15  42.904 ± 3.018  ops/s

测试在 MacbookPro(2.2GHz Intel Core i7,16Gb DDR3)和 JDK 1.8.0_73 上运行。

更新

我使用 JDK 9-ea b134 运行相同的测试。两项测试均显示速度提高了约 10%,但两者之间的速度差异仍然相似。

# JMH 1.13 (released 45 days ago)
# VM version: JDK 9-ea, VM 9-ea+134
# VM invoker: /Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/bin/java
# VM options: <none>


Benchmark                                        Mode  Cnt    Score    Error  Units
DirectByteBufferReadBenchmark.testReadAbsolute  thrpt   15  102.170 ± 10.199  ops/s
DirectByteBufferReadBenchmark.testReadRelative  thrpt   15   45.988 ±  3.896  ops/s

【问题讨论】:

    标签: java performance jvm microbenchmark jmh


    【解决方案1】:

    JDK 8 确实为具有相对 ByteBuffer 访问权限的循环生成了更糟糕的代码。

    JMH 具有内置的perfasm 分析器,可打印为最热区域生成的汇编代码。我有used it to compare 编译后的testReadAbsolutetestReadRelative,主要区别如下:

    1. 相对getLong / getInt/ get 更新ByteBuffer 的位置字段。 VM 没有优化这些更新:每次循环迭代有 3 次内存写入。

    2. position 范围检查未消除:每次循环迭代的条件分支仍保留在编译代码中。

    3. 由于冗余字段更新和范围检查使循环体更长,VM 仅展开循环的 2 次迭代。具有绝对访问权限的循环的编译版本展开了 16 次迭代。

    testReadAbsolute 编译得非常好:主循环只读取 16 个 long,将它们相加,如果 index &lt; 10_000_000 - 16 则跳转到下一次迭代。 directByteBuffer 的状态未更新。但是,JVM 对于testReadRelative 来说并不那么聪明:似乎它无法从外部优化对象的字段访问。

    JDK 9 中有很多工作来优化 ByteBuffer。我在 JDK 9-ea b134 上运行了相同的测试,并验证 testReadRelative 没有冗余内存写入和范围检查。现在它的运行速度几乎和testReadAbsolute 一样快。

    // JDK 1.8.0_92, VM 25.92-b14
    
    Benchmark                                        Mode  Cnt   Score   Error  Units
    DirectByteBufferReadBenchmark.testReadAbsolute  thrpt   10  99,727 ± 0,542  ops/s
    DirectByteBufferReadBenchmark.testReadRelative  thrpt   10  47,126 ± 0,289  ops/s
    
    // JDK 9-ea, VM 9-ea+134
    
    Benchmark                                        Mode  Cnt    Score   Error  Units
    DirectByteBufferReadBenchmark.testReadAbsolute  thrpt   10  109,369 ± 0,403  ops/s
    DirectByteBufferReadBenchmark.testReadRelative  thrpt   10   97,140 ± 0,572  ops/s
    

    更新

    为了帮助 JIT 编译器进行优化,我引入了局部变量

    ByteBuffer directByteBuffer = d.directByteBuffer
    

    在两个基准测试中。否则间接级别不允许编译器消除ByteBuffer.position 字段更新。

    【讨论】:

    • 感谢您的回答。我确实使用 JDK 9 进行了测试,看到了问题中的更新,但是我看不到相对读取的性能要好得多。任何想法为什么?
    • @VladimirG。是的,我的基准确实有点不同。我已经更新了答案。原因还是一样的:JIT 不会优化 position 字段的更新,这就是为什么相对 ByteBuffer 访问似乎效率较低的原因。
    猜你喜欢
    • 2015-10-16
    • 1970-01-01
    • 2010-09-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-01
    相关资源
    最近更新 更多