Điều gì làm cho các cuộc gọi JNI chậm?


194

Tôi biết rằng 'vượt qua các ranh giới' khi thực hiện cuộc gọi JNI trong Java là chậm.

Tuy nhiên tôi muốn biết cái gì làm cho nó chậm? Việc triển khai jvm cơ bản làm gì khi thực hiện cuộc gọi JNI khiến nó quá chậm?


2
(+1) Câu hỏi hay. Trong khi chúng ta đang ở chủ đề này, tôi muốn khuyến khích bất cứ ai đã thực hiện các tiêu chuẩn thực tế để đăng phát hiện của họ.
NPE

2
Một lệnh gọi JNI cần chuyển đổi các đối tượng Java được truyền sang một thứ C (ví dụ) có thể hiểu được; cùng với giá trị trả về. Chuyển đổi kiểu và sắp xếp ngăn xếp cuộc gọi là một đoạn tốt của nó.
Dave Newton

Dave, tôi hiểu và đã nghe về điều đó trước đây. Nhưng chính xác thì chuyển đổi như thế nào? "cái gì đó" là gì? Tôi đang tìm kiếm chi tiết.
pdeva

Sử dụng ByteBuffers trực tiếp để truyền dữ liệu giữa Java và C có thể dẫn đến chi phí tương đối thấp.
Peter Lawrey

6
cuộc gọi cần một khung ngăn xếp C thích hợp, đẩy tất cả các thanh ghi CPU hữu ích (và bật chúng trở lại), cuộc gọi cần đấu kiếm và nó cũng ngăn chặn rất nhiều tối ưu hóa như nội tuyến. Ngoài ra, các luồng phải rời khỏi khóa ngăn xếp thực thi (ví dụ để cho phép các khóa thiên vị hoạt động trong khi ở mã gốc) và sau đó lấy lại nó.
bestsss

Câu trả lời:


174

Đầu tiên, đáng chú ý là "chậm", chúng ta đang nói về thứ gì đó có thể mất hàng chục nano giây. Đối với các phương thức gốc không quan trọng, năm 2010 tôi đã đo các cuộc gọi ở mức trung bình 40 ns trên máy tính để bàn Windows của tôi và 11 ns trên máy tính để bàn Mac của tôi. Trừ khi bạn thực hiện nhiều cuộc gọi, bạn sẽ không chú ý.

Điều đó nói rằng, việc gọi một phương thức riêng có thể chậm hơn so với thực hiện một cuộc gọi phương thức Java thông thường. Nguyên nhân bao gồm:

  • Các phương thức riêng sẽ không được JVM nội tuyến. Chúng cũng sẽ không được biên dịch đúng lúc cho máy cụ thể này - chúng đã được biên dịch.
  • Một mảng Java có thể được sao chép để truy cập bằng mã gốc và sau đó được sao chép lại. Chi phí có thể là tuyến tính trong kích thước của mảng. Tôi đã đo JNI sao chép một mảng 100.000 đến trung bình khoảng 75 micro giây trên máy tính để bàn Windows của tôi và 82 micro giây trên máy Mac. May mắn thay, truy cập trực tiếp có thể được lấy thông qua GetPrimitiveArrayCritical hoặc NewDirectByteBuffer .
  • Nếu phương thức được truyền vào một đối tượng hoặc cần thực hiện một cuộc gọi lại, thì phương thức gốc có thể sẽ tự thực hiện các cuộc gọi đến JVM. Truy cập các trường, phương thức và kiểu Java từ mã gốc yêu cầu một cái gì đó tương tự như sự phản chiếu. Chữ ký được chỉ định trong chuỗi và truy vấn từ JVM. Điều này là cả chậm dễ bị lỗi.
  • Chuỗi Java là các đối tượng, có độ dài và được mã hóa. Truy cập hoặc tạo một chuỗi có thể yêu cầu một bản sao O (n).

Một số thảo luận bổ sung, có thể là ngày, có thể được tìm thấy trong "Hiệu suất nền tảng Java: Chiến lược và chiến thuật", 2000, bởi Steve Wilson và Jeff Kesselman, trong phần "9.2: Kiểm tra chi phí JNI". Đó là khoảng một phần ba của trang này , được cung cấp trong bình luận của @Philip bên dưới.

Bài viết trên IBM 2009 "Các cách thực hành tốt nhất để sử dụng Giao diện gốc Java" cung cấp một số gợi ý về cách tránh các cạm bẫy hiệu năng với JNI.


1
Câu trả lời này khẳng định, một số mã riêng có thể được JVM nội tuyến.
AH

5
Câu trả lời đó lưu ý rằng một số mã gốc tiêu chuẩn được nội tuyến trong JVM thay vì sử dụng JNI. Ở trên, "phương thức gốc" đề cập đến trường hợp chung của phương thức gốc do người dùng định nghĩa được triển khai thông qua JNI. Cảm ơn con trỏ đến sun.misc.Unsafe.
Andy Thomas

Tôi không muốn tuyên bố rằng phương pháp này có thể được sử dụng cho mọi cuộc gọi của JNI. Nhưng nó sẽ không làm tổn thương để biết rằng có một số mặt đất giữa giữa bytecode tinh khiết và mã JNI tinh khiết. Có lẽ điều này sẽ ảnh hưởng đến một số quyết định thiết kế. Có lẽ cơ chế này được khái quát trong tương lai.
AH

3
@AH, bạn nhầm lẫn nội tâm w / JNI. Họ khá khác nhau. sun.misc.Unsafevà khá nhiều thứ khác như System.currentTimeMillis/nanoTimeđược xử lý thông qua 'ma thuật' bởi JVM. Chúng không phải là JNI và chúng hoàn toàn không có các tệp .c / .h phù hợp, tự nó ẩn hàm JVM. Cách tiếp cận không thể được theo sau trừ khi bạn đang viết / hack JVM.
bestsss

1
" tài liệu java.sun.com " này hiện đang bị hỏng-- đây là một liên kết hoạt động.
Philip Guin

25

Điều đáng nói là không phải tất cả các phương thức Java được đánh dấu nativelà "chậm". Một số trong số họ là nội tại làm cho họ cực kỳ nhanh chóng. Để kiểm tra xem cái nào là nội tại và cái nào không, bạn có thể tìm do_intrinsictại vmSymbols.hpp .


23

Về cơ bản, JVM xây dựng một cách diễn giải các tham số C cho mỗi lệnh gọi JNI và mã không được tối ưu hóa.

Có nhiều chi tiết được nêu trong bài báo này

Nếu bạn quan tâm đến điểm chuẩn JNI so với mã gốc , dự án này có mã để chạy điểm chuẩn.


2
bài báo mà bạn liên kết đến có vẻ giống như một bài viết chuẩn về hiệu suất hơn là một bài viết mô tả cách JNI hoạt động bên trong.
pdeva

@pdeva Thật không may, các tài nguyên khác mà tôi tìm thấy được liên kết với java.sun.com và các liên kết chưa được cập nhật kể từ khi mua lại Oracle. Tôi đang tìm kiếm thêm thông tin chi tiết về nội bộ của JNI.
dmck

13
Bài viết về Java 1.3 - cách đây khá lâu. Các vấn đề thời đó có còn áp dụng cho Java 7 không?
AH
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.