Pertanyaan: Apakah penanganan pengecualian di Jawa sebenarnya lambat?
Kearifan konvensional, serta banyak hasil Google, mengatakan bahwa logika yang luar biasa tidak boleh digunakan untuk aliran program normal di Jawa. Dua alasan biasanya diberikan,
- itu sangat lambat - bahkan urutan besarnya lebih lambat dari kode biasa (alasan yang diberikan bervariasi),
dan
- itu berantakan karena orang hanya mengharapkan kesalahan ditangani dalam kode yang luar biasa.
Pertanyaan ini tentang # 1.
Sebagai contoh, halaman ini menjelaskan penanganan pengecualian Java sebagai "sangat lambat" dan menghubungkan kelambatan dengan pembuatan string pesan pengecualian - "string ini kemudian digunakan dalam membuat objek pengecualian yang dilemparkan. Ini tidak cepat." Artikel Penanganan Pengecualian Efektif di Jawa mengatakan bahwa "alasan untuk ini adalah karena aspek penciptaan objek penanganan pengecualian, yang dengan demikian membuat pengecualian melempar secara inheren lambat". Alasan lain di luar sana adalah bahwa generasi jejak tumpukan adalah apa yang memperlambatnya.
Pengujian saya (menggunakan Java 1.6.0_07, Java HotSpot 10.0, pada Linux 32 bit), menunjukkan bahwa penanganan pengecualian tidak lebih lambat dari kode biasa. Saya mencoba menjalankan metode dalam satu loop yang mengeksekusi beberapa kode. Di akhir metode, saya menggunakan boolean untuk menunjukkan apakah akan kembali atau melempar . Dengan cara ini, proses yang sebenarnya sama. Saya mencoba menjalankan metode dalam urutan yang berbeda dan rata-rata waktu pengujian saya, berpikir itu mungkin pemanasan JVM. Dalam semua pengujian saya, lemparan setidaknya secepat pengembalian, jika tidak lebih cepat (hingga 3,1% lebih cepat). Saya benar-benar terbuka dengan kemungkinan bahwa pengujian saya salah, tetapi saya belum melihat apa pun di luar sana dalam hal contoh kode, perbandingan pengujian, atau hasil dalam satu atau dua tahun terakhir yang menunjukkan penanganan pengecualian di Jawa untuk benar-benar menjadi lambat.
Apa yang menuntun saya ke jalan ini adalah API yang saya butuhkan untuk menggunakan itu melemparkan pengecualian sebagai bagian dari logika kontrol normal. Saya ingin memperbaikinya dalam penggunaannya, tetapi sekarang saya mungkin tidak dapat memperbaikinya. Akankah saya malah harus memuji mereka pada pemikiran ke depan mereka?
Dalam makalah penanganan Efisien Java dalam kompilasi just-in-time , penulis menyarankan bahwa kehadiran handler pengecualian saja, bahkan jika tidak ada pengecualian yang dilemparkan, cukup untuk mencegah kompiler JIT dari mengoptimalkan kode dengan benar, sehingga memperlambatnya . Saya belum menguji teori ini.
sumber
exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow
- memberikan penjelasan lengkap dan luas mengapa. Dan dia adalah orang yang menulis Java lib. Karena itu, dialah yang menentukan kontrak API kelas. Saya setuju dengan Bill K untuk hal ini.Jawaban:
Itu tergantung bagaimana pengecualian diterapkan. Cara paling sederhana adalah menggunakan setjmp dan longjmp. Itu berarti semua register CPU dituliskan ke stack (yang sudah memakan waktu) dan mungkin beberapa data lain perlu dibuat ... semua ini sudah terjadi dalam pernyataan coba. Pernyataan melempar perlu melepaskan tumpukan dan mengembalikan nilai-nilai semua register (dan kemungkinan nilai-nilai lain di VM). Jadi coba dan lempar sama lambatnya, dan itu cukup lambat, namun jika tidak ada pengecualian yang dilemparkan, keluar dari blok uji coba tidak memerlukan waktu apa pun dalam kebanyakan kasus (karena semuanya diletakkan di tumpukan yang membersihkan secara otomatis jika metode itu ada).
Sun dan yang lainnya mengakui, bahwa ini mungkin suboptimal dan tentu saja VMs menjadi lebih cepat dan lebih cepat dari waktu ke waktu. Ada cara lain untuk menerapkan pengecualian, yang membuat coba itu sendiri cepat kilat (sebenarnya tidak ada yang terjadi untuk dicoba sama sekali secara umum - semua yang perlu terjadi sudah dilakukan ketika kelas dimuat oleh VM) dan itu membuat melempar tidak terlalu lambat . Saya tidak tahu JVM mana yang menggunakan teknik baru yang lebih baik ini ...
... tetapi apakah Anda menulis dalam Java sehingga kode Anda nanti hanya berjalan pada satu JVM pada satu sistem tertentu? Karena jika mungkin pernah berjalan di platform lain atau versi JVM lainnya (mungkin dari vendor lain), siapa bilang mereka juga menggunakan implementasi cepat? Yang cepat lebih rumit daripada yang lambat dan tidak mudah pada semua sistem. Anda ingin tetap portabel? Maka jangan mengandalkan pengecualian yang cepat.
Ini juga membuat perbedaan besar apa yang Anda lakukan dalam blok percobaan. Jika Anda membuka blok try dan tidak pernah memanggil metode apa pun dari dalam blok try ini, blok try akan sangat cepat, karena JIT kemudian dapat benar-benar memperlakukan lemparan seperti goto sederhana. Tidak perlu menyimpan tumpukan-negara juga tidak perlu melepas tumpukan jika pengecualian dilemparkan (hanya perlu melompat ke penangan tangkapan). Namun, ini bukan yang biasa Anda lakukan. Biasanya Anda membuka blok try dan kemudian memanggil metode yang mungkin membuang pengecualian, kan? Dan bahkan jika Anda hanya menggunakan blok percobaan dalam metode Anda, metode seperti apa ini, yang tidak memanggil metode lain? Apakah hanya menghitung angka? Lalu untuk apa Anda membutuhkan pengecualian? Ada banyak cara yang lebih elegan untuk mengatur aliran program. Untuk hal lain selain matematika sederhana,
Lihat kode tes berikut:
Hasil:
Perlambatan dari blok uji coba terlalu kecil untuk mengesampingkan faktor pembaur seperti proses latar belakang. Tapi blok penahan membunuh semuanya dan membuatnya 66 kali lebih lambat!
Seperti yang saya katakan, hasilnya tidak akan seburuk itu jika Anda mencoba / menangkap dan melempar semua dalam metode yang sama (method3), tetapi ini adalah optimasi JIT khusus yang tidak akan saya andalkan. Dan bahkan ketika menggunakan optimasi ini, lemparannya masih sangat lambat. Jadi saya tidak tahu apa yang Anda coba lakukan di sini, tetapi pasti ada cara yang lebih baik untuk melakukannya daripada menggunakan try / catch / throw.
sumber
nanoTime()
membutuhkan Java 1.5 dan saya hanya memiliki Java 1.4 yang tersedia pada sistem yang saya gunakan untuk menulis kode di atas. Juga tidak memainkan peran besar dalam latihan. Satu-satunya perbedaan antara keduanya adalah bahwa satu nanodetik dan milidetik lainnya dan yangnanoTime
tidak dipengaruhi oleh manipulasi jam (yang tidak relevan, kecuali jika Anda atau proses sistem memodifikasi jam sistem tepat pada saat kode tes berjalan). Namun, secara umum Anda benar,nanoTime
tentu saja merupakan pilihan yang lebih baik.try
blok, tetapi tidakthrow
.throw
Tes Anda melemparkan pengecualian 50% dari waktu ia melewatitry
. Itu jelas situasi di mana kegagalan itu tidak luar biasa . Memotong itu menjadi hanya 10% secara besar-besaran memotong hit kinerja. Masalah dengan tes semacam ini adalah tes ini mendorong orang untuk berhenti menggunakan pengecualian sama sekali. Menggunakan pengecualian, untuk penanganan kasus yang luar biasa, berkinerja jauh lebih baik daripada yang ditunjukkan oleh tes Anda.return
. Itu meninggalkan metode di suatu tempat di tengah tubuh, mungkin bahkan di tengah operasi (yang sejauh ini hanya diselesaikan oleh 50%) dancatch
blok mungkin 20 tumpukan bingkai ke atas (metode memilikitry
blok, memanggil metode1, yang memanggil method2, yang memanggil mehtod3, ..., dan di method20 di tengah operasi pengecualian dilemparkan). Tumpukan harus dilepas 20 frame ke atas, semua operasi yang belum selesai harus dibatalkan (operasi tidak boleh setengah selesai) dan register CPU harus dalam keadaan bersih. Ini semua menghabiskan waktu.FYI, saya memperpanjang percobaan yang dilakukan Mecki:
3 yang pertama sama dengan Mecki (laptop saya jelas lebih lambat).
method4 identik dengan method3 kecuali bahwa ia menciptakan
new Integer(1)
daripada melakukanthrow new Exception()
.Method5 seperti method3 kecuali ia menciptakan
new Exception()
tanpa membuangnya.method6 seperti method3 kecuali bahwa ia melempar pengecualian yang sudah dibuat sebelumnya (variabel instance) daripada membuat yang baru.
Di Jawa, banyak biaya untuk melempar eksepsi adalah waktu yang dihabiskan untuk mengumpulkan jejak stack, yang terjadi ketika objek eksepsi dibuat. Biaya aktual untuk melemparkan pengecualian, meskipun besar, jauh lebih kecil daripada biaya untuk membuat pengecualian.
sumber
Aleksey Shipilëv melakukan analisis yang sangat menyeluruh di mana ia memberikan tolok ukur pengecualian Java dalam berbagai kombinasi kondisi:
Dia juga membandingkannya dengan kinerja memeriksa kode kesalahan pada berbagai tingkat frekuensi kesalahan.
Kesimpulannya (dikutip kata demi kata dari jabatannya) adalah:
Pengecualian yang benar-benar luar biasa adalah penampil yang cantik. Jika Anda menggunakannya sebagaimana dirancang, dan hanya mengomunikasikan kasus-kasus yang benar-benar luar biasa di antara sejumlah besar kasus tidak-luar biasa yang ditangani oleh kode biasa, maka menggunakan pengecualian adalah kinerja yang menang.
Biaya kinerja pengecualian memiliki dua komponen utama: konstruksi jejak tumpukan ketika Exception dipakai dan tumpukan tidak dibuka selama lemparan Exception.
Biaya konstruksi jejak tumpukan sebanding dengan kedalaman tumpukan pada saat instantiasi pengecualian. Itu sudah buruk karena siapa di Bumi yang tahu kedalaman tumpukan di mana metode pelemparan ini akan disebut? Bahkan jika Anda mematikan pembuatan jejak stack dan / atau cache pengecualian, Anda hanya dapat menyingkirkan bagian ini dari biaya kinerja.
Biaya pelonggaran tumpukan bergantung pada seberapa beruntungnya kita dengan mendekatkan penangan pengecualian dalam kode yang dikompilasi. Dengan hati-hati menyusun kode untuk menghindari pencarian penangan pengecualian yang dalam mungkin membantu kita menjadi lebih beruntung.
Jika kita menghilangkan kedua efek, biaya kinerja pengecualian adalah biaya cabang lokal. Tidak peduli seberapa indah kedengarannya, itu tidak berarti Anda harus menggunakan Pengecualian sebagai aliran kontrol yang biasa, karena dalam kasus itu Anda berada di tangan mengoptimalkan kompiler! Anda hanya boleh menggunakannya dalam kasus yang benar-benar luar biasa, di mana frekuensi pengecualian mengamortisasi kemungkinan biaya tidak beruntung untuk meningkatkan pengecualian yang sebenarnya.
Aturan praktis yang optimis tampaknya frekuensi 10 ^ -4 untuk pengecualian cukup luar biasa. Itu, tentu saja, tergantung pada bobot perkecualian itu sendiri, tindakan tepat yang diambil oleh penangan pengecualian, dll.
Hasilnya adalah bahwa ketika pengecualian tidak dilontarkan, Anda tidak membayar biaya, jadi ketika kondisi luar biasa cukup jarang, penanganan pengecualian lebih cepat daripada menggunakan
if
setiap waktu. Posting lengkap sangat layak dibaca.sumber
Sayangnya, jawaban saya terlalu panjang untuk dikirim di sini. Jadi izinkan saya meringkas di sini dan merujuk Anda ke http://www.fuwjax.com/how-slow-are-java-exceptions/ untuk rincian lebih lanjut.
Pertanyaan sebenarnya di sini bukanlah "Seberapa lambat 'kegagalan dilaporkan sebagai pengecualian' dibandingkan dengan 'kode yang tidak pernah gagal'?" karena respons yang diterima mungkin membuat Anda percaya. Alih-alih, pertanyaannya adalah "Seberapa lambat 'kegagalan dilaporkan sebagai pengecualian' dibandingkan dengan kegagalan yang dilaporkan dengan cara lain?" Secara umum, dua cara lain untuk melaporkan kegagalan adalah dengan nilai sentinel atau dengan pembungkus hasil.
Nilai sentinel adalah upaya untuk mengembalikan satu kelas dalam hal kesuksesan dan yang lain dalam hal kegagalan. Anda dapat menganggapnya hampir seperti mengembalikan pengecualian daripada melemparkannya. Ini membutuhkan kelas induk bersama dengan objek sukses dan kemudian melakukan pemeriksaan "instanceof" dan pasangan berperan untuk mendapatkan informasi keberhasilan atau kegagalan.
Ternyata dengan risiko keamanan tipe, nilai Sentinel lebih cepat dari pengecualian, tetapi hanya dengan faktor sekitar 2x. Sekarang, itu mungkin tampak seperti banyak, tetapi 2x itu hanya menutupi biaya perbedaan implementasi. Dalam praktiknya, faktornya jauh lebih rendah karena metode kami yang mungkin gagal jauh lebih menarik daripada beberapa operator aritmatika seperti dalam kode contoh di tempat lain di halaman ini.
Pembungkus Hasil, di sisi lain, tidak mengorbankan keselamatan jenis sama sekali. Mereka membungkus informasi keberhasilan dan kegagalan dalam satu kelas. Jadi alih-alih "instanceof" mereka menyediakan "isSuccess ()" dan getter untuk objek keberhasilan dan kegagalan. Namun, objek hasil kira-kira 2x lebih lambat daripada menggunakan pengecualian. Ternyata membuat objek pembungkus baru setiap kali jauh lebih mahal daripada melemparkan pengecualian.
Selain itu, pengecualian adalah bahasa yang disediakan untuk menunjukkan bahwa suatu metode mungkin gagal. Tidak ada cara lain untuk mengetahui dari API mana metode yang diharapkan untuk selalu (kebanyakan) bekerja dan yang diharapkan untuk melaporkan kegagalan.
Pengecualian lebih aman daripada penjaga, lebih cepat dari objek hasil, dan kurang mengejutkan daripada keduanya. Saya tidak menyarankan bahwa coba / tangkap ganti jika / yang lain, tetapi pengecualian adalah cara yang tepat untuk melaporkan kegagalan, bahkan dalam logika bisnis.
Yang mengatakan, saya ingin menunjukkan bahwa dua cara yang paling sering mempengaruhi kinerja yang secara substansial saya jalankan adalah menciptakan objek yang tidak perlu dan loop bersarang. Jika Anda memiliki pilihan antara membuat pengecualian atau tidak membuat pengecualian, jangan membuat pengecualian. Jika Anda memiliki pilihan antara membuat pengecualian kadang-kadang atau membuat objek lain sepanjang waktu, maka buatlah pengecualian.
sumber
Saya telah memperluas jawaban yang diberikan oleh @Mecki dan @incarnate , tanpa mengisi stacktrace untuk Java.
Dengan Java 7+, kita bisa menggunakan
Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace)
. Tetapi untuk Java6, lihat jawaban saya untuk pertanyaan iniOutput dengan Java 1.6.0_45, pada Core i7, 8GB RAM:
Jadi, masih metode yang mengembalikan nilai lebih cepat, dibandingkan dengan metode melempar pengecualian. IMHO, kami tidak dapat mendesain API yang jelas hanya menggunakan tipe pengembalian untuk aliran kesuksesan & kesalahan. Metode yang melempar pengecualian tanpa stacktrace adalah 4-5 kali lebih cepat dari pengecualian normal.
Sunting: NoStackTraceThrowable.java Terima kasih @Greg
sumber
public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
With Java 7+, we can use
tetapi kemudian Anda menulisOutput with Java 1.6.0_45,
jadi ini adalah hasil Java 6 atau 7?Throwable
konstruktor yang memilikiboolean writableStackTrace
arg. Tapi itu tidak ada di Java 6 dan di bawah. Itu sebabnya saya telah memberikan implementasi kustom untuk Java 6 dan di bawah. Jadi kode di atas adalah untuk Java 6 & di bawah ini. Harap baca baris ke-1 para ke-2 dengan seksama.Optional
atau serupa datang agak terlambat ke Jawa. Sebelumnya kami juga menggunakan objek wrapper dengan flag sukses / kesalahan. Tapi sepertinya ini sedikit retas dan tidak terasa alami bagi saya.Beberapa waktu yang lalu saya menulis sebuah kelas untuk menguji kinerja relatif dari mengubah string ke int menggunakan dua pendekatan: (1) panggil Integer.parseInt () dan tangkap pengecualian, atau (2) cocokkan string dengan regex dan panggil parseInt () hanya jika pertandingan berhasil. Saya menggunakan regex dengan cara paling efisien yang saya bisa (yaitu, membuat objek Pola dan Matcher sebelum intering loop), dan saya tidak mencetak atau menyimpan stacktraces dari pengecualian.
Untuk daftar sepuluh ribu string, jika semuanya adalah angka yang valid, pendekatan parseInt () empat kali lebih cepat dari pendekatan regex. Tetapi jika hanya 80% dari string yang valid, regex dua kali lebih cepat dari parseInt (). Dan jika 20% valid, artinya pengecualian dilemparkan dan ditangkap 80% dari waktu, regex sekitar dua puluh kali lebih cepat dari parseInt ().
Saya terkejut dengan hasilnya, mengingat bahwa pendekatan regex memproses string yang valid dua kali: sekali untuk pertandingan dan sekali lagi untuk parseInt (). Tapi melempar dan menangkap pengecualian lebih dari dibuat untuk itu. Situasi semacam ini tidak mungkin terjadi sangat sering di dunia nyata, tetapi jika itu terjadi, Anda pasti tidak harus menggunakan teknik penangkapan pengecualian. Tetapi jika Anda hanya memvalidasi input pengguna atau sesuatu seperti itu, tentu saja gunakan pendekatan parseInt ().
sumber
Integer.ParseInt()
) dan saya berharap bahwa sebagian besar kali input pengguna akan benar, jadi untuk kasus penggunaan saya sepertinya mengambil pengecualian sesekali hit adalah cara untuk pergi .Saya pikir artikel pertama merujuk pada tindakan melintasi tumpukan panggilan dan membuat jejak tumpukan sebagai bagian yang mahal, dan sementara artikel kedua tidak mengatakannya, saya pikir itu adalah bagian paling mahal dari pembuatan objek. John Rose memiliki artikel di mana ia menjelaskan berbagai teknik untuk mempercepat pengecualian . (Preallocating dan menggunakan kembali pengecualian, pengecualian tanpa jejak tumpukan, dll)
Tapi tetap saja - saya pikir ini harus dianggap hanya kejahatan yang diperlukan, pilihan terakhir. Alasan John untuk melakukan ini adalah untuk meniru fitur-fitur dalam bahasa lain yang belum tersedia di JVM. Anda TIDAK boleh membiasakan diri menggunakan pengecualian untuk aliran kontrol. Terutama bukan karena alasan kinerja! Seperti yang Anda sebutkan di # 2, Anda berisiko menutupi bug serius dalam kode Anda dengan cara ini, dan akan lebih sulit untuk mempertahankannya untuk programmer baru.
Microbenchmark di Jawa secara mengejutkan sulit untuk mendapatkan yang benar (saya telah diberitahu), terutama ketika Anda masuk ke wilayah JIT, jadi saya benar-benar ragu bahwa menggunakan pengecualian lebih cepat daripada "kembali" dalam kehidupan nyata. Sebagai contoh, saya menduga Anda memiliki antara 2 dan 5 frame stack dalam pengujian Anda? Sekarang bayangkan kode Anda akan dipanggil oleh komponen JSF yang digunakan oleh JBoss. Sekarang Anda mungkin memiliki jejak tumpukan yang panjangnya beberapa halaman.
Mungkin Anda dapat memposting kode pengujian Anda?
sumber
Tidak tahu apakah topik ini berhubungan, tapi saya pernah ingin menerapkan satu trik dengan mengandalkan jejak tumpukan thread saat ini: Saya ingin menemukan nama metode ini, yang memicu instantiasi di dalam kelas instantiated (ya, idenya gila, Saya benar-benar menyerah). Jadi saya menemukan bahwa panggilan
Thread.currentThread().getStackTrace()
adalah sangat lambat (karena aslidumpThreads
metode yang digunakan secara internal).Jadi Java
Throwable
, dengan demikian, memiliki metode aslifillInStackTrace
. Saya berpikir bahwacatch
blok pembunuh yang dijelaskan sebelumnya entah bagaimana memicu eksekusi metode ini.Tapi izinkan saya menceritakan kisah lain kepada Anda ...
Dalam Scala beberapa fitur fungsional dikompilasi menggunakan JVM
ControlThrowable
, yang memperluasThrowable
dan menimpanya denganfillInStackTrace
cara berikut:Jadi saya mengadaptasi tes di atas (jumlah siklus berkurang sepuluh, mesin saya sedikit lebih lambat :):
Jadi, hasilnya adalah:
Anda lihat, satu-satunya perbedaan antara
method3
danmethod4
adalah bahwa mereka membuang berbagai jenis pengecualian. Ya,method4
masih lebih lambat daripadamethod1
danmethod2
, tetapi perbedaannya jauh lebih dapat diterima.sumber
Saya telah melakukan beberapa pengujian kinerja dengan JVM 1.5 dan menggunakan pengecualian setidaknya 2x lebih lambat. Rata-rata: Waktu eksekusi pada metode kecil yang sepele lebih dari tiga kali lipat (3x) dengan pengecualian. Sebuah loop kecil yang sepele yang harus menangkap pengecualian melihat peningkatan 2x dalam waktu.
Saya telah melihat angka yang sama dalam kode produksi serta tolok ukur mikro.
Pengecualian harus TIDAK digunakan untuk apa pun yang sering disebut. Melemparkan ribuan pengecualian per detik akan menyebabkan leher botol besar.
Misalnya, menggunakan "Integer.ParseInt (...)" untuk menemukan semua nilai buruk dalam file teks yang sangat besar - ide yang sangat buruk. (Saya telah melihat metode utilitas ini mematikan kinerja pada kode produksi)
Menggunakan pengecualian untuk melaporkan nilai buruk pada formulir GUI pengguna, mungkin tidak terlalu buruk dari sudut pandang kinerja.
Baik itu praktik desain yang baik, saya akan mengikuti aturan: jika kesalahannya normal / diharapkan, maka gunakan nilai balik. Jika tidak normal, gunakan pengecualian. Misalnya: membaca input pengguna, nilai buruk adalah normal - gunakan kode kesalahan. Melewati nilai ke fungsi utilitas internal, nilai buruk harus difilter dengan memanggil kode - gunakan pengecualian.
sumber
Kinerja pengecualian di Java dan C # meninggalkan banyak hal yang diinginkan.
Sebagai programmer ini memaksa kita untuk hidup dengan aturan "pengecualian harus disebabkan jarang", hanya karena alasan kinerja praktis.
Namun, sebagai ilmuwan komputer, kita harus memberontak terhadap kondisi bermasalah ini. Orang yang menulis suatu fungsi seringkali tidak tahu seberapa sering akan dipanggil, atau apakah sukses atau gagal lebih mungkin. Hanya penelepon yang memiliki informasi ini. Mencoba untuk menghindari pengecualian mengarah pada idom API yang tidak jelas di mana dalam beberapa kasus kami hanya memiliki versi pengecualian yang bersih tapi lambat, dan dalam kasus lain kami memiliki kesalahan nilai pengembalian yang cepat tapi kikuk, dan dalam kasus lain kami berakhir dengan keduanya . Pelaksana perpustakaan mungkin harus menulis dan memelihara dua versi API, dan penelepon harus memutuskan mana dari dua versi yang akan digunakan dalam setiap situasi.
Ini agak berantakan. Jika pengecualian memiliki kinerja yang lebih baik, kita dapat menghindari idiom-idiom yang kikuk ini dan menggunakan pengecualian karena itu dimaksudkan untuk digunakan ... sebagai fasilitas pengembalian kesalahan yang terstruktur.
Saya benar-benar ingin melihat mekanisme pengecualian diimplementasikan menggunakan teknik yang lebih dekat ke nilai-kembali, sehingga kami dapat memiliki kinerja yang lebih dekat untuk mengembalikan nilai .. karena ini adalah apa yang kami kembalikan ke dalam kode sensitif kinerja.
Berikut ini adalah contoh kode yang membandingkan kinerja pengecualian dengan kinerja nilai pengembalian kesalahan.
TestIt kelas publik {
}
Dan inilah hasilnya:
Memeriksa dan menyebarkan kembali nilai tidak menambahkan beberapa biaya vs panggilan baseline-nol, dan biaya yang proporsional dengan kedalaman panggilan. Pada kedalaman rantai panggilan 8, versi pengecekan nilai-kembali-kesalahan sekitar 27% lebih lambat dari versi dasar yang tidak memeriksa nilai-nilai kembali.
Kinerja pengecualian, sebagai perbandingan, bukan fungsi dari kedalaman panggilan, tetapi dari frekuensi pengecualian. Namun, penurunan seiring meningkatnya frekuensi pengecualian jauh lebih dramatis. Pada frekuensi kesalahan hanya 25%, kode berjalan 24-TIMES lebih lambat. Pada frekuensi kesalahan 100%, versi pengecualian hampir 100 kali lebih lambat.
Ini menunjukkan kepada saya bahwa mungkin membuat tradeoff yang salah dalam implementasi pengecualian kami. Pengecualian bisa lebih cepat, baik dengan menghindari jalan setapak yang mahal, atau dengan langsung mengubahnya menjadi penyusun yang didukung pemeriksaan nilai pengembalian. Sampai mereka melakukannya, kita terjebak menghindarinya ketika kita ingin kode kita berjalan cepat.
sumber
HotSpot cukup mampu menghapus kode pengecualian untuk pengecualian yang dihasilkan sistem, asalkan semua itu sebaris. Namun, pengecualian yang dibuat secara eksplisit dan yang tidak dihapus menghabiskan banyak waktu membuat jejak stack. Ganti
fillInStackTrace
untuk melihat bagaimana ini dapat memengaruhi kinerja.sumber
Bahkan jika melempar pengecualian tidak lambat, itu masih ide yang buruk untuk membuang pengecualian untuk aliran program normal. Digunakan dengan cara ini analog dengan GOTO ...
Saya kira itu tidak benar-benar menjawab pertanyaan itu. Saya akan membayangkan bahwa kebijaksanaan 'konvensional' untuk melempar pengecualian menjadi lambat adalah benar dalam versi java sebelumnya (<1.4). Membuat pengecualian mengharuskan VM membuat seluruh jejak stack. Banyak yang telah berubah sejak saat itu di VM untuk mempercepat dan ini mungkin salah satu area yang telah ditingkatkan.
sumber
break
ataureturn
, bukan agoto
.Bandingkan saja katakanlah Integer.parseInt dengan metode berikut ini, yang hanya mengembalikan nilai default dalam kasus data yang tidak dapat diparsing alih-alih melemparkan Pengecualian:
Selama Anda menerapkan kedua metode untuk data "valid", keduanya akan bekerja pada laju yang kira-kira sama (meskipun Integer.parseInt mengelola untuk menangani data yang lebih kompleks). Tetapi begitu Anda mencoba mem-parsing data yang tidak valid (mis. Untuk mem-parsing "abc" 1.000.000 kali), perbedaan kinerja haruslah penting.
sumber
Pos bagus tentang kinerja pengecualian adalah:
https://shipilev.net/blog/2014/exceptional-performance/
Instantiating vs menggunakan kembali yang ada, dengan jejak stack dan tanpa, dll:
Bergantung pada kedalaman jejak tumpukan:
Untuk detail lainnya (termasuk assembler x64 dari JIT) baca posting blog asli.
Itu berarti Hibernate / Spring / etc-EE-shit lambat karena pengecualian (xD) dan menulis ulang aliran kontrol aplikasi menjauh dari pengecualian (ganti dengan
continure
/break
dan mengembalikanboolean
bendera seperti dalam C dari panggilan metode) meningkatkan kinerja aplikasi Anda 10x-100x , tergantung pada seberapa sering Anda melemparnya))sumber
Saya mengubah @Mecki jawaban di atas untuk memiliki method1 mengembalikan boolean dan cek dalam metode panggilan, karena Anda tidak bisa hanya mengganti Pengecualian dengan apa pun. Setelah dua kali berjalan, method1 masih merupakan yang tercepat atau secepat metode2.
Ini adalah snapshot dari kode tersebut:
dan hasil:
Jalankan 1
Jalankan 2
sumber
Sebuah pengecualian dimaksudkan untuk menangani kondisi yang tidak terduga pada saat dijalankan saja.
Menggunakan pengecualian menggantikan validasi sederhana yang dapat dilakukan dalam waktu kompilasi akan menunda validasi hingga waktu eksekusi. Ini pada gilirannya akan mengurangi efisiensi program.
Melempar pengecualian alih-alih menggunakan validasi if..else yang sederhana juga akan membuat kode menjadi kompleks untuk ditulis dan dipelihara.
sumber
Pendapat saya tentang kecepatan Exception versus mengecek data secara terprogram.
Banyak kelas memiliki String to value converter (pemindai / pengurai), perpustakaan yang dihormati dan juga terkenal;)
biasanya memiliki bentuk
nama pengecualian hanya contoh, biasanya tidak dicentang (runtime), jadi melempar deklarasi hanya gambar saya
terkadang ada bentuk kedua:
tidak pernah melempar
Ketika insur kedua tidak tersedia (atau programmer membaca terlalu sedikit dokumen dan hanya menggunakan yang pertama), tulis kode seperti itu dengan ekspresi reguler. Ekspresi reguler itu keren, benar secara politis, dll:
dengan kode ini, pemrogram tidak memiliki biaya pengecualian. TAPI TELAH sebanding biaya yang sangat tinggi untuk ekspresi reguler SELALU versus biaya pengecualian kecil kadang-kadang.
Saya menggunakan hampir selalu dalam konteks seperti itu
tanpa menganalisis stacktrace dll, saya percaya setelah kuliah Anda cukup cepat.
Jangan Takut Pengecualian
sumber
Mengapa pengecualian harus lebih lambat dari pengembalian normal?
Selama Anda tidak mencetak stacktrace ke terminal, menyimpannya ke dalam file atau sesuatu yang serupa, blok-catch tidak bekerja lebih dari blok-kode lainnya. Jadi, saya tidak bisa membayangkan mengapa "melempar my_cool_error ()" yang baru harus lambat.
Pertanyaan bagus dan saya menantikan informasi lebih lanjut tentang topik ini!
sumber