Saya terus melihat orang mengatakan bahwa pengecualian itu lambat, tetapi saya tidak pernah melihat bukti apa pun. Jadi, alih-alih menanyakan apakah ada, saya akan bertanya bagaimana cara kerja pengecualian di belakang layar, sehingga saya dapat membuat keputusan kapan harus menggunakannya dan apakah lambat.
Dari apa yang saya tahu, pengecualian sama dengan melakukan pengembalian beberapa kali, kecuali bahwa itu juga memeriksa setelah setiap pengembalian apakah perlu melakukan yang lain atau berhenti. Bagaimana cara memeriksa kapan harus berhenti kembali? Saya kira ada tumpukan kedua yang menyimpan jenis pengecualian dan lokasi tumpukan, itu kemudian mengembalikan sampai tiba di sana. Saya juga menebak bahwa satu-satunya saat tumpukan kedua ini disentuh adalah pada lemparan dan pada setiap percobaan / tangkapan. AFAICT yang menerapkan perilaku serupa dengan kode pengembalian akan membutuhkan waktu yang sama. Tapi ini semua hanya tebakan, jadi saya ingin tahu apa yang sebenarnya terjadi.
Bagaimana cara kerja pengecualian?
Jawaban:
Alih-alih menebak-nebak, saya memutuskan untuk benar-benar melihat kode yang dihasilkan dengan sepotong kecil kode C ++ dan instalasi Linux yang agak lama.
Saya mengkompilasinya dengan
g++ -m32 -W -Wall -O3 -save-temps -c
, dan melihat file assembly yang dihasilkan._ZN11MyExceptionD1Ev
adalahMyException::~MyException()
, jadi kompilator memutuskan bahwa ia memerlukan salinan destruktor non-inline.Mengherankan! Tidak ada instruksi tambahan sama sekali di jalur kode normal. Kompiler malah menghasilkan blok kode perbaikan out-of-line ekstra, yang direferensikan melalui tabel di akhir fungsi (yang sebenarnya diletakkan di bagian terpisah dari executable). Semua pekerjaan dilakukan di belakang layar oleh pustaka standar, berdasarkan tabel-tabel
_ZTI11MyException
initypeinfo for MyException
.Oke, itu sebenarnya bukan kejutan bagi saya, saya sudah tahu bagaimana kompiler ini melakukannya. Melanjutkan dengan keluaran perakitan:
Di sini kita melihat kode untuk melakukan pengecualian. Meskipun tidak ada biaya tambahan hanya karena pengecualian mungkin dilemparkan, jelas ada banyak biaya tambahan dalam benar-benar melempar dan menangkap pengecualian. Sebagian besar tersembunyi di dalam
__cxa_throw
, yang harus:Bandingkan itu dengan biaya hanya mengembalikan nilai, dan Anda melihat mengapa pengecualian harus digunakan hanya untuk pengembalian yang luar biasa.
Untuk menyelesaikan, sisa file assembly:
Data typeinfo.
Bahkan lebih banyak tabel penanganan pengecualian, dan berbagai informasi tambahan.
Jadi, kesimpulannya, setidaknya untuk GCC di Linux: biaya adalah ruang ekstra (untuk penangan dan tabel) terlepas dari apakah pengecualian dilempar atau tidak, ditambah biaya tambahan untuk mengurai tabel dan menjalankan penangan saat pengecualian dilempar. Jika Anda menggunakan pengecualian, bukan kode kesalahan, dan kesalahan jarang terjadi, ini bisa lebih cepat , karena Anda tidak memiliki overhead untuk menguji kesalahan lagi.
Jika Anda menginginkan informasi lebih lanjut, khususnya apa yang dilakukan semua
__cxa_
fungsi, lihat spesifikasi asli asalnya:sumber
Pengecualian lambat memang benar di masa lalu.
Dalam kebanyakan kompilator modern, hal ini tidak berlaku lagi.
Catatan: Hanya karena kami memiliki pengecualian tidak berarti kami tidak menggunakan kode kesalahan juga. Ketika kesalahan dapat ditangani secara lokal, gunakan kode kesalahan. Ketika kesalahan membutuhkan lebih banyak konteks untuk koreksi, gunakan pengecualian: Saya menulisnya dengan lebih fasih di sini: Prinsip-prinsip apa yang memandu kebijakan penanganan pengecualian Anda?
Biaya kode penanganan pengecualian ketika tidak ada pengecualian yang digunakan praktis nol.
Saat pengecualian dilemparkan, ada beberapa pekerjaan yang dilakukan.
Tetapi Anda harus membandingkan ini dengan biaya pengembalian kode kesalahan dan memeriksa semuanya kembali ke titik di mana kesalahan dapat ditangani. Keduanya lebih memakan waktu untuk menulis dan memelihara.
Juga ada satu gotcha untuk pemula:
Meskipun objek Exception seharusnya kecil, beberapa orang memasukkan banyak barang ke dalamnya. Kemudian Anda memiliki biaya untuk menyalin objek pengecualian. Solusinya ada dua kali lipat:
Menurut pendapat saya, saya berani bertaruh bahwa kode yang sama dengan pengecualian lebih efisien atau setidaknya sebanding dengan kode tanpa pengecualian (tetapi memiliki semua kode tambahan untuk memeriksa hasil kesalahan fungsi). Ingat Anda tidak mendapatkan apa pun secara gratis, kompilator menghasilkan kode yang seharusnya Anda tulis di tempat pertama untuk memeriksa kode kesalahan (dan biasanya kompilator jauh lebih efisien daripada manusia).
sumber
Ada beberapa cara untuk menerapkan pengecualian, tetapi biasanya cara tersebut bergantung pada beberapa dukungan mendasar dari OS. Di Windows, ini adalah mekanisme penanganan pengecualian terstruktur.
Ada diskusi yang layak tentang detail pada Proyek Kode: Bagaimana kompilator C ++ mengimplementasikan penanganan pengecualian
Overhead of exception terjadi karena compiler harus menghasilkan kode untuk melacak objek mana yang harus dihancurkan di setiap frame tumpukan (atau lebih tepatnya cakupan) jika pengecualian menyebar keluar dari cakupan itu. Jika suatu fungsi tidak memiliki variabel lokal pada stack yang memerlukan pemanggilan destructors maka ia seharusnya tidak memiliki penanganan pengecualian wrt penalti kinerja.
Menggunakan kode kembali hanya dapat melepas satu tingkat tumpukan pada satu waktu, sedangkan mekanisme penanganan pengecualian dapat melompat lebih jauh ke bawah tumpukan dalam satu operasi jika tidak ada yang bisa dilakukannya dalam bingkai tumpukan menengah.
sumber
Matt Pietrek menulis artikel yang sangat bagus tentang Win32 Structured Exception Handling . Meskipun artikel ini aslinya ditulis pada tahun 1997, itu masih berlaku sampai sekarang (tetapi tentu saja hanya berlaku untuk Windows).
sumber
Artikel ini membahas masalah dan pada dasarnya menemukan bahwa dalam praktiknya ada biaya run-time untuk pengecualian, meskipun biayanya cukup rendah jika pengecualian tidak dilemparkan. Artikel bagus, direkomendasikan.
sumber
Seorang teman saya menulis sedikit bagaimana Visual C ++ menangani pengecualian beberapa tahun yang lalu.
http://www.xyzw.de/c160.html
sumber
Semua jawaban bagus.
Selain itu, pikirkan tentang betapa lebih mudahnya men-debug kode yang melakukan 'if check' sebagai gerbang di bagian atas metode alih-alih mengizinkan kode untuk melempar pengecualian.
Moto saya adalah mudah untuk menulis kode yang berhasil. Yang paling penting adalah menulis kode untuk orang berikutnya yang melihatnya. Dalam beberapa kasus, ini adalah Anda dalam 9 bulan, dan Anda tidak ingin mengutuk nama Anda!
sumber