2012-06-24 46 views

Trả lời

22

Được lấy/đặt từ bộ lọc không trực tiếp không nhanh hơn nhận/đặt từ bộ đệm trực tiếp không?

Nếu bạn so sánh bộ đệm heap với bộ đệm trực tiếp không sử dụng thứ tự byte gốc (hầu hết các hệ thống là ít endian và mặc định cho ByteBuffer trực tiếp là lớn endian), hiệu suất là rất giống nhau.

Nếu bạn sử dụng bộ đệm byte theo thứ tự gốc, hiệu suất có thể tốt hơn đáng kể cho các giá trị nhiều byte. Đối với byte nó làm cho sự khác biệt nhỏ không có vấn đề gì bạn làm.

Trong HotSpot/OpenJDK, ByteBuffer sử dụng lớp Không an toàn và nhiều phương pháp native được coi là intrinsics. Đây là JVM phụ thuộc và AFAIK máy ảo Android xử lý nó như là một bản chất trong các phiên bản gần đây.

Nếu bạn đổ cụm được tạo ra, bạn có thể thấy nội tại trong Không an toàn được bật trong một hướng dẫn mã máy. tức là họ không có phí của cuộc gọi JNI.

Trong thực tế, nếu bạn đang điều chỉnh vi mô, bạn có thể thấy rằng hầu hết thời gian của ByteBuffer getXxxx hoặc setXxxx là chi tiêu trong việc kiểm tra giới hạn, không phải truy cập bộ nhớ thực. Vì lý do này, tôi vẫn sử dụng không an toàn trực tiếp khi tôi phải cho hiệu suất tối đa (Lưu ý: đây không được khuyến khích bởi Oracle)

Nếu tôi phải đọc/ghi từ ByteBuffer trực tiếp, nó là tốt hơn để đầu đọc/viết vào một mảng byte cục bộ luồng và sau đó cập nhật (để viết) trực tiếp bằng dấu gạch ngang đầy đủ với mảng byte?

Tôi rất ghét xem điều gì tốt hơn. ;) Nghe có vẻ rất phức tạp.

Thường thì các giải pháp đơn giản sẽ tốt hơn và nhanh hơn.


Bạn có thể tự mình kiểm tra mã này.

public static void main(String... args) { 
    ByteBuffer bb1 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder()); 
    ByteBuffer bb2 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder()); 
    for (int i = 0; i < 10; i++) 
     runTest(bb1, bb2); 
} 

private static void runTest(ByteBuffer bb1, ByteBuffer bb2) { 
    bb1.clear(); 
    bb2.clear(); 
    long start = System.nanoTime(); 
    int count = 0; 
    while (bb2.remaining() > 0) 
     bb2.putInt(bb1.getInt()); 
    long time = System.nanoTime() - start; 
    int operations = bb1.capacity()/4 * 2; 
    System.out.printf("Each putInt/getInt took an average of %.1f ns%n", (double) time/operations); 
} 

in

Each putInt/getInt took an average of 83.9 ns 
Each putInt/getInt took an average of 1.4 ns 
Each putInt/getInt took an average of 34.7 ns 
Each putInt/getInt took an average of 1.3 ns 
Each putInt/getInt took an average of 1.2 ns 
Each putInt/getInt took an average of 1.3 ns 
Each putInt/getInt took an average of 1.2 ns 
Each putInt/getInt took an average of 1.2 ns 
Each putInt/getInt took an average of 1.2 ns 
Each putInt/getInt took an average of 1.2 ns 

Tôi khá chắc chắn một cuộc gọi JNI mất nhiều thời gian hơn 1,2 ns.


Để chứng minh rằng nó không phải là cuộc gọi "JNI" nhưng guff xung quanh nó gây ra sự chậm trễ. Bạn có thể viết cùng một vòng lặp bằng cách sử dụng Unsafe trực tiếp.

public static void main(String... args) { 
    ByteBuffer bb1 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder()); 
    ByteBuffer bb2 = ByteBuffer.allocateDirect(256 * 1024).order(ByteOrder.nativeOrder()); 
    for (int i = 0; i < 10; i++) 
     runTest(bb1, bb2); 
} 

private static void runTest(ByteBuffer bb1, ByteBuffer bb2) { 
    Unsafe unsafe = getTheUnsafe(); 
    long start = System.nanoTime(); 
    long addr1 = ((DirectBuffer) bb1).address(); 
    long addr2 = ((DirectBuffer) bb2).address(); 
    for (int i = 0, len = Math.min(bb1.capacity(), bb2.capacity()); i < len; i += 4) 
     unsafe.putInt(addr1 + i, unsafe.getInt(addr2 + i)); 
    long time = System.nanoTime() - start; 
    int operations = bb1.capacity()/4 * 2; 
    System.out.printf("Each putInt/getInt took an average of %.1f ns%n", (double) time/operations); 
} 

public static Unsafe getTheUnsafe() { 
    try { 
     Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); 
     theUnsafe.setAccessible(true); 
     return (Unsafe) theUnsafe.get(null); 
    } catch (Exception e) { 
     throw new AssertionError(e); 
    } 
} 

in

Each putInt/getInt took an average of 40.4 ns 
Each putInt/getInt took an average of 44.4 ns 
Each putInt/getInt took an average of 0.4 ns 
Each putInt/getInt took an average of 0.3 ns 
Each putInt/getInt took an average of 0.3 ns 
Each putInt/getInt took an average of 0.3 ns 
Each putInt/getInt took an average of 0.3 ns 
Each putInt/getInt took an average of 0.3 ns 
Each putInt/getInt took an average of 0.3 ns 
Each putInt/getInt took an average of 0.3 ns 

Vì vậy, bạn có thể thấy rằng các cuộc gọi native là nhanh hơn nhiều hơn bạn có thể mong đợi cho một cuộc gọi JNI. Lý do chính cho sự chậm trễ này có thể là tốc độ bộ nhớ cache L2. ;)

Tất cả chạy trên một i3 3,3 GHz

+0

Cảm ơn Peter. Điều này rất hữu ích. BTW, Tại sao oracle khuyên bạn không nên sử dụng Unsafe trực tiếp. Nếu chúng ta sử dụng nó trong mã sản xuất những gì cạm bẫy có thể phát sinh? – user882659

+0

Như tên cho thấy, không an toàn để sử dụng và lỗi có thể làm hỏng hệ thống. tức là nhanh hơn vì tất cả các biện pháp bảo vệ đều bị tắt. Những gì tôi làm là có hai triển khai, một trong đó sử dụng ByteBuffers như là và một trong đó sử dụng không an toàn. Khi tôi tin tưởng vào việc thử nghiệm phần mềm và tôi cần nó, bạn có thể thả vào phiên bản không an toàn. –

+3

Trong thực tế, tôi đã sử dụng Unsafe để cố tình làm hỏng hệ thống, ví dụ: Tôi muốn kiểm tra những gì sẽ xảy ra nếu ứng dụng gặp sự cố ** ở đây **;) –

2

Bộ đệm trực tiếp giữ dữ liệu trong đất JNI, do đó, get() và put() phải vượt qua ranh giới JNI. Một bộ đệm không trực tiếp chứa dữ liệu trong đất JVM.

Vì vậy:

  1. Nếu bạn không chơi với các dữ liệu ở tất cả trong đất Java, ví dụ chỉ cần sao chép kênh sang kênh khác, bộ đệm trực tiếp nhanh hơn, vì dữ liệu không bao giờ phải vượt qua ranh giới JNI chút nào.

  2. Ngược lại, nếu bạn đang chơi với dữ liệu trong vùng đất Java, bộ đệm không trực tiếp sẽ nhanh hơn. Liệu nó có ý nghĩa hay không phụ thuộc vào số lượng dữ liệu phải vượt qua ranh giới JNI và cả về lượng tử được chuyển mỗi lần. Ví dụ, nhận hoặc đặt một byte đơn tại một thời điểm từ/đến một bộ đệm trực tiếp có thể rất tốn kém, khi nhận/đặt 16384 byte tại một thời điểm sẽ phân bổ chi phí ranh giới JNI đáng kể.

Để trả lời đoạn thứ hai của bạn, tôi sẽ sử dụng một byte địa phương [] mảng, không phải là một thread-địa phương, nhưng sau đó nếu tôi được chơi với các dữ liệu trong đất Java tôi sẽ không sử dụng một bộ đệm byte trực tiếp ở tất cả. Như Javadoc nói, các bộ đệm byte trực tiếp chỉ nên được sử dụng khi chúng mang lại lợi ích hiệu suất có thể đo lường được.

+0

Cảm ơn, kích thước tin nhắn của tôi là thường 256 byte mà tôi muốn viết thư cho ổ cắm, tôi đã suy nghĩ về mã hóa các byte vào một sợi byte địa phương [] mảng và sau đó sao chép mảng byte vào một dấu gạch ngang trực tiếp và truyền trực tiếp mã thông báo tới kênh socket để ghi. – user882659

+0

bộ mã vạch trực tiếp được gộp chung. điều này có thích hợp hơn không hoặc bạn có nên mã hóa thông báo trực tiếp vào bộ gõ trực tiếp thay vì sử dụng mảng byte tạm thời – user882659

+0

@ user882659 Xem chỉnh sửa hay không. Không có lợi ích gì khi sử dụng bộ đệm trực tiếp trong trường hợp này. – EJP

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