Apa yang membuat panggilan JNI lambat?

194

Saya tahu bahwa 'melewati batas' ketika melakukan panggilan JNI di Jawa lambat.

Namun saya ingin tahu apa yang membuatnya lambat? Apa yang dilakukan implementasi jvm yang mendasari ketika membuat panggilan JNI yang membuatnya sangat lambat?

pdeva
sumber
2
(+1) Pertanyaan yang bagus. Sementara kita membahas masalah ini, saya ingin mendorong siapa saja yang telah melakukan tolok ukur aktual untuk memposting temuan mereka.
NPE
2
Panggilan JNI perlu mengkonversi objek Java yang diteruskan ke sesuatu yang C (misalnya) dapat dimengerti; sama dengan nilai pengembalian. Jenis konversi dan tumpukan panggilan marshalling adalah bagian yang baik.
Dave Newton
Dave, saya mengerti dan pernah mendengar tentang itu sebelumnya. Tetapi seperti apa tepatnya konversi itu? apa itu 'sesuatu'? Saya mencari detail.
pdeva
Menggunakan ByteBuffers langsung untuk mengirimkan data antara Java dan C dapat menghasilkan overhead yang relatif rendah.
Peter Lawrey
6
panggilan membutuhkan bingkai C stack yang tepat, mendorong semua register CPU yang berguna (dan mengembalikannya), panggilan membutuhkan pagar dan juga mencegah banyak optimasi seperti inline. Juga utas harus meninggalkan kunci tumpukan eksekusi (misalnya untuk memungkinkan kunci bias bekerja saat dalam kode asli) dan kemudian mendapatkannya kembali.
bestsss

Jawaban:

174

Pertama, perlu dicatat bahwa dengan "lambat," kita berbicara tentang sesuatu yang bisa memakan waktu puluhan nanodetik. Untuk metode asli sepele, pada tahun 2010 saya mengukur panggilan pada rata-rata 40 ns pada desktop Windows saya, dan 11 ns pada desktop Mac saya. Kecuali Anda melakukan banyak panggilan, Anda tidak akan menyadarinya.

Yang mengatakan, memanggil metode asli bisa lebih lambat daripada membuat panggilan metode Java normal. Penyebab meliputi:

  • Metode asli tidak akan diuraikan oleh JVM. Mereka juga tidak akan dikompilasi tepat waktu untuk mesin khusus ini - mereka sudah dikompilasi.
  • Array Java dapat disalin untuk akses dalam kode asli, dan kemudian disalin kembali. Biaya dapat linear dalam ukuran array. Saya mengukur menyalin JNI dari 100.000 array dengan rata-rata sekitar 75 mikrodetik pada desktop Windows saya, dan 82 mikrodetik pada Mac. Untungnya, akses langsung dapat diperoleh melalui GetPrimitiveArrayCritical atau NewDirectByteBuffer .
  • Jika metode ini meneruskan objek, atau perlu membuat panggilan balik, maka metode asli kemungkinan akan membuat panggilan sendiri ke JVM. Mengakses bidang Java, metode dan tipe dari kode asli memerlukan sesuatu yang mirip dengan refleksi. Tanda tangan ditentukan dalam string dan ditanyakan dari JVM. Ini lambat dan rentan kesalahan.
  • Java Strings adalah objek, memiliki panjang dan dikodekan. Mengakses atau membuat string mungkin memerlukan salinan O (n).

Beberapa diskusi tambahan, mungkin tanggal, dapat ditemukan di "Kinerja Platform Java: Strategi dan Taktik", 2000, oleh Steve Wilson dan Jeff Kesselman, di bagian "9.2: Memeriksa biaya JNI". Sekitar sepertiga dari jalan ke bawah halaman ini , disediakan dalam komentar oleh @ Philip di bawah

Makalah IBM developerWorks 2009 "Praktik terbaik untuk menggunakan Java Native Interface" memberikan beberapa saran untuk menghindari jebakan kinerja dengan JNI.

Andy Thomas
sumber
1
Jawaban ini mengklaim, bahwa beberapa kode asli dapat diuraikan oleh JVM.
AH
5
Jawaban itu mencatat bahwa beberapa kode asli standar dimasukkan dalam JVM daripada menggunakan JNI. Di atas, "metode asli" mengacu pada kasus umum metode asli yang ditentukan pengguna yang diimplementasikan melalui JNI. Terima kasih atas penunjuknya ke sun.misc.Unsafe.
Andy Thomas
Saya tidak ingin mengklaim, bahwa pendekatan ini dapat digunakan untuk setiap panggilan JNI. Tapi itu tidak akan sakit untuk mengetahui bahwa ada adalah beberapa jalan tengah antara bytecode murni dan kode JNI murni. Mungkin ini akan mempengaruhi beberapa keputusan desain. Mungkin mekanisme ini digeneralisasi di masa depan.
AH
3
@ Ah, Anda salah mengira intrinsik bersama JNI. Mereka sangat berbeda. sun.misc.Unsafedan cukup banyak hal-hal lain seperti System.currentTimeMillis/nanoTimeditangani melalui 'sihir' oleh JVM. Mereka bukan JNI dan mereka tidak memiliki file .c / .h yang tepat sama sekali, kecuali JVM impl, itu sendiri. Pendekatan tidak dapat diikuti kecuali Anda menulis / meretas JVM.
bestsss
1
" dokumen java.sun.com ini " saat ini rusak - inilah tautan yang berfungsi.
Philip Guin
25

Perlu disebutkan bahwa tidak semua metode Java yang ditandai dengan native"lambat". Beberapa dari mereka adalah intrinsik yang membuatnya sangat cepat. Untuk memeriksa mana yang intrinsik dan mana yang tidak, Anda dapat mencari do_intrinsicdi vmSymbols.hpp .

Tema
sumber
23

Pada dasarnya JVM secara interpretatif membuat parameter C untuk setiap panggilan JNI dan kode tidak dioptimalkan.

Ada banyak detail lainnya yang diuraikan dalam makalah ini

Jika Anda tertarik untuk membandingkan JNI vs kode asli , proyek ini memiliki kode untuk menjalankan benchmark.

dmck
sumber
2
makalah yang Anda tautkan tampaknya lebih seperti kertas tolok ukur kinerja daripada yang menggambarkan bagaimana JNI bekerja secara internal.
pdeva
@ pdeva Sayangnya sumber daya lain yang saya temukan ditautkan ke java.sun.com dan tautannya belum diperbarui sejak akuisisi Oracle. Saya mencari detail lebih lanjut tentang internal JNI.
dmck
13
Makalah ini tentang Java 1.3 - cukup lama. Apakah masalah waktu itu masih berlaku untuk Java 7?
AH