2016-09-04 5 views
14

直接java.nio.ByteBufferの読み取りパフォーマンスをテストしていたとき、私は絶対読み取りが相対読み取りよりも平均2倍速いことに気付きました。また、相対読み取りと絶対読み取りのソースコードを比較すると、相対読み取りと内部カウンタを除いてコードはほとんど同じです。なぜ私はスピードのこのような大きな違いを見ますか?以下は Direct ByteBuffer relativeと絶対読み取りパフォーマンス

は私の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(); 
    } 
} 

そして、これらは私のベンチマークの実行の結果である:テストはMacbookPro(2.2GHzのインテルCore上で実行された

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 

i7,16Gb DDR3)およびJDK 1.8.0_73をサポートしています。

UPDATE

私はJDK 9-EA B134と同じテストを実行します。どちらのテストも約10%のスピードアップを示していますが、2つのスピードの差は似ています。

# 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 

答えて

19

JDK 8は相対ByteBufferアクセス​​で実際にループのコードを悪化させます。

JMHには、最もホットな領域に対して生成されたアセンブリコードを印刷するperfasmプロファイラが組み込まれています。 ByteBuffer

  1. 相対getLong/getInt/ get更新位置フィールド:私は、主な違いをまとめtestReadAbsolutetestReadRelativeused it to compareをしました、そしてここにいます。 VMはこれらの更新を最適化しません。各ループの繰り返しで3つのメモリ書き込みがあります。

  2. position範囲チェックは削除されません。各ループ反復の条件分岐はコンパイルされたコードのままです。

  3. 冗長なフィールドの更新と範囲チェックによりループ本体が長くなるため、VMはループの2回の反復のみを展開します。絶対アクセスを持つループのコンパイルされたバージョンには、16回の反復がアンロールされています。

testReadAbsoluteは非常によくコンパイルされている:メインループは、ちょうど、16のlong型を読み込み、それらを合計し、index < 10_000_000 - 16場合は、次の繰り返しにジャンプします。 directByteBufferの状態は更新されません。しかし、JVMはそれほどスマートではありませんtestReadRelative:外部からのオブジェクトのフィールドアクセスを最適化できないようです。

ByteBufferを最適化するためにJDK 9には多くの作業がありました。私は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 

UPDATE

を最適化JITコンパイラを助けるために私は両方のベンチマークでは、ローカル変数

ByteBuffer directByteBuffer = d.directByteBuffer 

導入しました。それ以外のレベルの間接化では、コンパイラはByteBuffer.positionフィールドの更新を排除できません。

+0

ご回答ありがとうございます。私はJDK 9でテストしましたが、問題のアップデートを見ていますが、相対的な読み込みがはるかに優れているとは思いません。なぜどんなアイデア? –

+0

@VladimirG。はい、私のベンチマークは本当に少し異なっていました。私は答えを更新しました。理由は同じです.JITは 'position'フィールドの更新を最適化しません。そのため、相対ByteBufferアクセス​​は効率が悪いようです。 – apangin

関連する問題