Di Jawa, menggunakan lemparan / tangkap sebagai bagian dari logika ketika sebenarnya tidak ada kesalahan pada umumnya adalah ide yang buruk (sebagian) karena melempar dan menangkap pengecualian itu mahal, dan melakukannya berkali-kali dalam satu lingkaran biasanya jauh lebih lambat daripada yang lain mengontrol struktur yang tidak melibatkan pengecualian melempar.
Pertanyaan saya adalah, apakah biaya yang dikeluarkan dalam lemparan / tangkapan itu sendiri, atau ketika membuat objek Pengecualian (karena ia mendapatkan banyak informasi runtime termasuk tumpukan eksekusi)?
Dengan kata lain, jika saya melakukannya
Exception e = new Exception();
tetapi jangan membuangnya, apakah itu sebagian besar biaya melempar, atau apakah lemparan + tangkapan menangani apa yang mahal?
Saya tidak bertanya apakah memasukkan kode ke blok coba / tangkap menambah biaya mengeksekusi kode itu, saya bertanya apakah menangkap Exception adalah bagian yang mahal, atau membuat (memanggil konstruktor untuk) Exception adalah bagian yang mahal .
Cara lain untuk menanyakan hal ini adalah, jika saya membuat satu contoh Pengecualian dan melemparkan serta menangkapnya berulang kali, akankah itu jauh lebih cepat daripada membuat Pengecualian baru setiap kali saya melempar?
sumber
Jawaban:
Membuat objek pengecualian tidak lebih mahal daripada membuat objek biasa lainnya. Biaya utama disembunyikan dalam
fillInStackTrace
metode asli yang berjalan melalui tumpukan panggilan dan mengumpulkan semua informasi yang diperlukan untuk membangun jejak tumpukan: kelas, nama metode, nomor baris dll.Mitos tentang biaya pengecualian yang tinggi berasal dari kenyataan bahwa sebagian besar
Throwable
konstruktor secara implisit memanggilfillInStackTrace
. Namun, ada satu konstruktor untuk membuatThrowable
jejak tanpa tumpukan. Hal ini memungkinkan Anda untuk membuat throwables yang sangat cepat untuk instantiate. Cara lain untuk membuat pengecualian ringan adalah menimpanyafillInStackTrace
.Sekarang bagaimana dengan melempar pengecualian?
Bahkan, itu tergantung di mana pengecualian yang dilemparkan ditangkap .
Jika tertangkap dalam metode yang sama (atau, lebih tepatnya, dalam konteks yang sama, karena konteksnya dapat mencakup beberapa metode karena inlining), maka
throw
itu secepat dan sesederhanagoto
(tentu saja, setelah kompilasi JIT).Namun jika sebuah
catch
blok berada di suatu tempat lebih dalam di stack, maka JVM perlu melepaskan frame stack, dan ini bisa memakan waktu lebih lama secara signifikan. Dibutuhkan lebih lama lagi, jika adasynchronized
blok atau metode yang terlibat, karena melepas gulungan menyiratkan pelepasan monitor yang dimiliki oleh frame tumpukan yang dihapus.Saya dapat mengkonfirmasi pernyataan di atas dengan tolok ukur yang tepat, tetapi untungnya saya tidak perlu melakukan ini, karena semua aspek sudah tercakup dengan sempurna dalam jabatan insinyur kinerja HotSpot, Alexey Shipilev: Kinerja Luar Biasa Pengecualian Lil .
sumber
Operasi pertama di sebagian besar
Throwable
konstruktor adalah mengisi jejak tumpukan, yang merupakan tempat sebagian besar pengeluaran.Namun, ada konstruktor yang dilindungi dengan bendera untuk menonaktifkan jejak stack. Konstruktor ini dapat diakses saat memanjang
Exception
juga. Jika Anda membuat jenis pengecualian khusus, Anda dapat menghindari pembuatan jejak tumpukan dan mendapatkan kinerja yang lebih baik dengan mengorbankan lebih sedikit informasi.Jika Anda membuat pengecualian tunggal dari jenis apa pun dengan cara biasa, Anda dapat melemparkannya berkali-kali tanpa perlu mengisi jejak tumpukan. Namun, jejak tumpukannya akan mencerminkan di mana ia dibangun, bukan di mana itu dilemparkan dalam contoh tertentu.
Versi Java saat ini membuat beberapa upaya untuk mengoptimalkan pembuatan jejak stack. Kode asli dipanggil untuk mengisi jejak tumpukan, yang mencatat jejak dalam struktur asli yang lebih ringan. Sesuai Java
StackTraceElement
objek yang malas diciptakan dari catatan ini hanya ketikagetStackTrace()
,printStackTrace()
atau metode lain yang memerlukan jejak disebut.Jika Anda menghilangkan timbunan jejak, biaya utama lainnya adalah melepaskan tumpukan di antara lemparan dan tangkapan. Semakin sedikit bingkai intervensi yang ditemukan sebelum pengecualian ditangkap, semakin cepat ini terjadi.
Rancang program Anda sehingga pengecualian hanya dibuang dalam kasus yang benar-benar luar biasa, dan optimasi seperti ini sulit untuk dibenarkan.
sumber
Ada tulisan yang bagus tentang Pengecualian di sini.
http://shipilev.net/blog/2014/exceptional-performance/
Kesimpulannya adalah bahwa konstruksi tumpukan jejak dan tumpukan unwinding adalah bagian yang mahal. Kode di bawah ini memanfaatkan fitur di
1.7
mana kita dapat menghidupkan dan mematikan tumpukan jejak. Kita kemudian dapat menggunakan ini untuk melihat biaya seperti apa yang dimiliki berbagai skenarioBerikut ini adalah pengaturan waktu untuk pembuatan Obyek saja. Saya telah menambahkan di
String
sini sehingga Anda dapat melihat bahwa tanpa tumpukan yang ditulis hampir tidak ada perbedaan dalam membuatJavaException
Obyek dan aString
. Dengan penulisan stack dihidupkan perbedaannya dramatis yaitu setidaknya satu urutan besarnya lebih lambat.Berikut ini menunjukkan berapa lama untuk kembali dari lemparan pada kedalaman tertentu jutaan kali.
Berikut ini hampir pasti lebih dari penyederhanaan ...
Jika kita mengambil kedalaman 16 dengan penulisan tumpukan maka penciptaan objek memakan waktu ~ 40% dari waktu, jejak jejak sebenarnya menyumbang sebagian besar dari ini. ~ 93% dari instantiating objek JavaException disebabkan oleh jejak stack yang diambil. Ini berarti bahwa membuka tumpukan tumpukan dalam kasus ini menghabiskan 50% waktu lainnya.
Ketika kita mematikan stack trace, penciptaan objek menyumbang fraksi yang jauh lebih kecil, yaitu 20% dan stack unwinding kini merupakan 80% dari waktu.
Dalam kedua kasus, susunan pemunduran membutuhkan sebagian besar waktu keseluruhan.
Frame tumpukan dalam contoh ini kecil dibandingkan dengan apa yang biasanya Anda temukan.
Anda dapat mengintip bytecode menggunakan javap
yaitu ini untuk metode 4 ...
sumber
Penciptaan
Exception
dengannull
jejak stack memakan waktu sebanyakthrow
dantry-catch
blok bersama. Namun, mengisi jejak tumpukan memakan waktu rata-rata 5x lebih lama .Saya membuat tolok ukur berikut untuk menunjukkan dampak pada kinerja. Saya menambahkannya
-Djava.compiler=NONE
ke Run Configuration untuk menonaktifkan optimisasi kompiler. Untuk mengukur dampak dari membangun tumpukan jejak, saya memperluasException
kelas untuk mengambil keuntungan dari konstruktor bebas tumpukan:Kode benchmark adalah sebagai berikut:
Keluaran:
Ini menyiratkan bahwa membuat a
NoStackException
kira-kira sama mahal dengan berulang kali melemparkan yang samaException
. Ini juga menunjukkan bahwa membuatException
dan mengisi jejak stacknya memakan waktu sekitar 4x lebih lama.sumber
Ini bagian dari pertanyaan ...
Tampaknya akan bertanya apakah membuat pengecualian dan menyimpannya di suatu tempat meningkatkan kinerja. Ya itu. Ini sama dengan mematikan tumpukan yang sedang ditulis pada pembuatan objek karena sudah dilakukan.
Ini adalah timing yang saya dapatkan, silakan baca peringatan setelah ini ...
Tentu saja masalah dengan ini adalah jejak tumpukan Anda sekarang menunjuk ke tempat Anda membuat objek bukan tempat objek itu dilempar.
sumber
Menggunakan jawaban @ AustinD sebagai titik awal, saya melakukan beberapa penyesuaian. Kode di bawah.
Selain menambahkan kasus di mana satu instance Exception dilemparkan berulang kali, saya juga mematikan optimisasi kompiler sehingga kami bisa mendapatkan hasil kinerja yang akurat. Saya menambahkan
-Djava.compiler=NONE
argumen VM, sesuai jawaban ini . (Dalam gerhana, edit Run Configuration → Arguments untuk mengatur argumen VM ini)Hasil:
Jadi membuat biaya pengecualian sekitar 5x sebanyak membuang + menangkapnya. Dengan asumsi kompiler tidak mengoptimalkan banyak biaya.
Sebagai perbandingan, inilah uji coba yang sama tanpa menonaktifkan pengoptimalan:
Kode:
sumber