2016-09-04 20 views
14

Trong khi tôi đang thử nghiệm hiệu suất đọc của java.nio.ByteBuffer trực tiếp, tôi nhận thấy rằng giá trị đọc tuyệt đối trung bình gấp 2 lần so với đọc tương đối. Ngoài ra nếu tôi so sánh mã nguồn của tương đối so với đọc tuyệt đối, mã là khá nhiều giống nhau ngoại trừ việc đọc tương đối duy trì và truy cập nội bộ. Tôi tự hỏi tại sao tôi lại thấy sự khác biệt đáng kể về tốc độ?Trực tiếp ByteBuffer tương đối so với hiệu năng đọc tuyệt đối

Dưới đây là mã nguồn của JMH benchmark của tôi:

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

Và đây là kết quả của chạy benchmark của tôi:

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 

thử nghiệm được chạy trên một MacbookPro (2.2GHz Intel Core i7, 16Gb DDR3) và JDK 1.8.0_73.

CẬP NHẬT

tôi chạy thử nghiệm cùng với JDK b134 9-ea. Cả hai thử nghiệm cho thấy một tăng tốc độ 10% nhưng sự khác biệt tốc độ giữa hai vẫn tương tự.

# 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 

Trả lời

19

JDK 8 thực sự tạo mã tồi tệ hơn cho vòng lặp có quyền truy cập ByteBuffer tương đối.

JMH đã tích hợp sẵn perfasm trình tạo hồ sơ in mã lắp ráp được tạo cho các khu vực nóng nhất. Tôi đã used it to compare các biên soạn testReadAbsolute vs testReadRelative, và đây là những khác biệt chính:

  1. tương đối getLong/getInt/ get cập nhật vị trí lĩnh vực ByteBuffer. VM không tối ưu hóa các bản cập nhật này: có 3 bộ nhớ ghi trên mỗi vòng lặp lặp lại.

  2. position kiểm tra phạm vi không được loại bỏ: các nhánh có điều kiện trên mỗi vòng lặp lặp lại vẫn còn trong mã được biên dịch.

  3. Vì cập nhật trường thừa và kiểm tra phạm vi làm cho vòng lặp dài hơn, VM chỉ ghi lại 2 lần lặp của vòng lặp. Phiên bản được biên dịch cho vòng lặp có quyền truy cập tuyệt đối có 16 lần lặp lại không được kiểm soát.

testReadAbsolute được biên soạn rất tốt: vòng lặp chính chỉ đọc 16 chờ đợi, tóm chúng lên và nhảy đến phiên bản kế tiếp nếu index < 10_000_000 - 16. Trạng thái directByteBuffer không được cập nhật. Tuy nhiên, JVM không phải là thông minh cho testReadRelative: có vẻ như nó không thể tối ưu hóa truy cập trường của một đối tượng từ bên ngoài.

Có nhiều công việc trong JDK 9 để tối ưu hóa ByteBuffer. Tôi đã chạy thử nghiệm tương tự trên JDK 9-ea b134 và đã xác minh rằng testReadRelative không có ghi bộ nhớ dư thừa và kiểm tra phạm vi. Bây giờ nó chạy gần như nhanh như 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 

CẬP NHẬT

Nhằm giúp trình biên dịch JIT với tối ưu hóa tôi đã giới thiệu biến địa phương

ByteBuffer directByteBuffer = d.directByteBuffer 

trong cả hai tiêu chuẩn. Nếu không, mức độ gián tiếp không cho phép trình biên dịch loại bỏ các cập nhật trường ByteBuffer.position.

+0

cảm ơn câu trả lời của bạn. Tôi đã thử nghiệm với JDK 9, xem cập nhật trong câu hỏi, tuy nhiên tôi không thấy người đọc tương đối để thực hiện tốt hơn nhiều. Bất kỳ ý tưởng tại sao? –

+0

@VladimirG. Vâng, điểm chuẩn của tôi thực sự hơi khác một chút. Tôi đã cập nhật câu trả lời. Lý do vẫn giống nhau: JIT không tối ưu hóa các cập nhật của trường 'position', đó là lý do tại sao việc truy cập ByteBuffer tương đối dường như kém hiệu quả hơn. – apangin

Các vấn đề liên quan