Saya menonton Systematic Error Handling di C ++ - Andrei Alexandrescu dia mengklaim bahwa Pengecualian di C ++ sangat lambat.
Apakah ini masih berlaku untuk C ++ 98?
Saya menonton Systematic Error Handling di C ++ - Andrei Alexandrescu dia mengklaim bahwa Pengecualian di C ++ sangat lambat.
Apakah ini masih berlaku untuk C ++ 98?
Jawaban:
Model utama yang digunakan saat ini untuk pengecualian (Itanium ABI, VC ++ 64 bit) adalah pengecualian model Biaya-Nol.
Idenya adalah bahwa alih-alih kehilangan waktu dengan menyiapkan penjaga dan secara eksplisit memeriksa keberadaan pengecualian di mana-mana, kompilator membuat tabel samping yang memetakan titik mana pun yang dapat memunculkan pengecualian (Penghitung Program) ke daftar penangan. Ketika pengecualian dilemparkan, daftar ini dikonsultasikan untuk memilih penangan kanan (jika ada) dan tumpukan dibatalkan.
Dibandingkan dengan if (error)strategi tipikal :
ifketika pengecualian terjadiNamun, biayanya tidak sepele untuk diukur:
dynamic_casttes untuk setiap handler)Jadi, sebagian besar cache meleset, dan karenanya tidak sepele dibandingkan dengan kode CPU murni.
Catatan: untuk lebih jelasnya, baca laporan TR18015, bab 5.4 Penanganan Pengecualian (pdf)
Jadi, ya, pengecualian lambat di jalur luar biasa , tetapi sebaliknya lebih cepat daripada pemeriksaan eksplisit ( ifstrategi) pada umumnya.
Catatan: Andrei Alexandrescu sepertinya mempertanyakan ini "lebih cepat". Saya pribadi telah melihat hal-hal berayun ke dua arah, beberapa program menjadi lebih cepat dengan pengecualian dan yang lainnya lebih cepat dengan cabang, jadi memang tampaknya ada hilangnya pengoptimalan dalam kondisi tertentu.
Apakah itu penting?
Saya akan mengklaim tidak. Sebuah program harus ditulis dengan mempertimbangkan keterbacaan , bukan kinerja (setidaknya, bukan sebagai kriteria pertama). Pengecualian akan digunakan ketika seseorang mengharapkan bahwa pemanggil tidak dapat atau tidak ingin menangani kegagalan saat itu juga, dan meneruskannya ke tumpukan. Bonus: dalam C ++ 11 pengecualian dapat diatur antar utas menggunakan Perpustakaan Standar.
Ini halus, saya mengklaim itu map::findtidak boleh melempar tetapi saya baik-baik saja dengan map::findmengembalikan checked_ptryang melempar jika upaya untuk dereferensi gagal karena nol: dalam kasus terakhir, seperti dalam kasus kelas yang diperkenalkan Alexandrescu, pemanggil memilih antara pemeriksaan eksplisit dan mengandalkan pengecualian. Memberdayakan penelepon tanpa memberinya lebih banyak tanggung jawab biasanya merupakan tanda desain yang baik.
abortakan memungkinkan Anda mengukur footprint ukuran biner dan memeriksa bahwa waktu muat / i-cache berperilaku serupa. Tentu saja, lebih baik tidak memukul salah satu abort...
Ketika pertanyaan itu diposting, saya sedang dalam perjalanan ke dokter, dengan taksi menunggu, jadi saya hanya punya waktu untuk komentar singkat. Tetapi setelah memberi komentar dan memberi suara positif dan suara negatif, saya lebih baik menambahkan jawaban saya sendiri. Meskipun jawaban Matthieu sudah cukup bagus.
Kembali klaim
“Saya menonton Systematic Error Handling di C ++ - Andrei Alexandrescu dia mengklaim bahwa Pengecualian di C ++ sangat lambat.”
Jika itu benar-benar yang diklaim Andrei, maka untuk kali ini dia sangat menyesatkan, jika tidak salah. Untuk pengecualian yang dimunculkan / dilempar selalu lambat dibandingkan dengan operasi dasar lainnya dalam bahasa tersebut, apa pun bahasa pemrogramannya . Tidak hanya di C ++ atau lebih di C ++ daripada di bahasa lain, seperti yang ditunjukkan klaim yang diklaim.
Secara umum, sebagian besar terlepas dari bahasanya, dua fitur bahasa dasar yang lipat lebih lambat dari yang lain, karena mereka menerjemahkan panggilan rutinitas yang menangani struktur data yang kompleks,
pengecualian melempar, dan
alokasi memori dinamis.
Untungnya di C ++ orang sering dapat menghindari keduanya dalam kode kritis waktu.
Sayangnya Tidak Ada Hal Seperti Makan Siang Gratis , bahkan jika efisiensi default C ++ cukup mendekati. :-) Untuk efisiensi yang diperoleh dengan menghindari pelemparan eksepsi dan alokasi memori dinamis umumnya dicapai dengan pengkodean pada tingkat abstraksi yang lebih rendah, menggunakan C ++ hanya sebagai "C yang lebih baik". Dan abstraksi yang lebih rendah berarti "kompleksitas" yang lebih besar.
Kompleksitas yang lebih besar berarti lebih banyak waktu yang dihabiskan untuk pemeliharaan dan sedikit atau tidak ada manfaat dari penggunaan kembali kode, yang merupakan biaya moneter yang nyata, meskipun sulit untuk diperkirakan atau diukur. Yaitu, dengan C ++ seseorang dapat, jika diinginkan, memperdagangkan beberapa efisiensi programmer untuk efisiensi eksekusi. Apakah untuk melakukannya sebagian besar merupakan keputusan rekayasa dan naluri, karena dalam praktiknya hanya keuntungannya, bukan biayanya, yang dapat dengan mudah diperkirakan dan diukur.
Ya, komite standardisasi C ++ internasional telah menerbitkan Laporan Teknis tentang kinerja C ++, TR18015 .
Terutama itu berarti bahwa seseorang throwdapat mengambil Very Long Time ™ dibandingkan dengan misalnya sebuah inttugas, karena pencarian penangan.
Seperti yang dibahas TR18015 di bagian 5.4 "Pengecualian", ada dua strategi implementasi penanganan pengecualian utama,
pendekatan di mana setiap tryblok secara dinamis menyiapkan penangkapan pengecualian, sehingga pencarian rantai dinamis penangan dilakukan ketika pengecualian dilemparkan, dan
pendekatan di mana kompilator menghasilkan tabel pencarian statis yang digunakan untuk menentukan penangan untuk pengecualian yang dilemparkan.
Pendekatan pertama yang sangat fleksibel dan umum hampir dipaksakan di Windows 32-bit, sedangkan di lahan 64-bit dan di * nix-land, pendekatan kedua yang jauh lebih efisien biasanya digunakan.
Juga seperti yang dibahas dalam laporan tersebut, untuk setiap pendekatan ada tiga bidang utama di mana penanganan pengecualian berdampak pada efisiensi:
try-blocks,
fungsi reguler (peluang pengoptimalan), dan
throw-ekspresi.
Terutama, dengan pendekatan penangan dinamis (Windows 32-bit) penanganan pengecualian berdampak pada tryblok, sebagian besar terlepas dari bahasanya (karena ini dipaksa oleh skema Penanganan Pengecualian Terstruktur Windows ), sedangkan pendekatan tabel statis kira-kira memiliki biaya nol untuk try- blok. Membahas hal ini akan membutuhkan lebih banyak ruang dan penelitian daripada praktis untuk jawaban SO. Jadi, lihat laporannya untuk detailnya.
Sayangnya, laporan dari tahun 2006 sudah sedikit tertanggal hingga akhir 2012, dan sejauh yang saya tahu tidak ada yang sebanding yang lebih baru.
Perspektif penting lainnya adalah bahwa dampak penggunaan pengecualian terhadap kinerja sangat berbeda dari efisiensi yang terisolasi dari fitur bahasa pendukung, karena, seperti catatan laporan,
“Saat mempertimbangkan penanganan pengecualian, ini harus dibandingkan dengan cara alternatif untuk menangani kesalahan.”
Sebagai contoh:
Biaya pemeliharaan karena gaya pemrograman yang berbeda (kebenaran)
ifPemeriksaan kegagalan situs panggilan yang berlebihan versus terpusattry
Masalah cache (mis. Kode yang lebih pendek mungkin muat di cache)
Laporan tersebut memiliki daftar aspek yang berbeda untuk dipertimbangkan, tetapi satu-satunya cara praktis untuk mendapatkan fakta nyata tentang efisiensi eksekusi mungkin dengan mengimplementasikan program yang sama menggunakan pengecualian dan tidak menggunakan pengecualian, dalam batas waktu pengembangan yang ditentukan, dan dengan pengembang akrab dengan masing-masing cara, dan kemudian MENGUKUR .
Ketepatan hampir selalu mengalahkan efisiensi.
Tanpa pengecualian, hal berikut dapat dengan mudah terjadi:
Beberapa kode P dimaksudkan untuk mendapatkan sumber daya atau menghitung beberapa informasi.
Kode panggilan C seharusnya telah memeriksa keberhasilan / kegagalan, tetapi tidak.
Sumber daya yang tidak ada atau informasi yang tidak valid digunakan dalam kode setelah C, menyebabkan kekacauan umum.
Masalah utamanya adalah poin (2), dimana dengan skema kode pengembalian yang biasa kode pemanggil C tidak dipaksa untuk diperiksa.
Ada dua pendekatan utama yang memaksa pemeriksaan seperti itu:
Di mana P secara langsung melempar pengecualian saat gagal.
Di mana P mengembalikan objek yang harus diperiksa C sebelum menggunakan nilai utamanya (jika tidak pengecualian atau penghentian).
Pendekatan kedua adalah, AFAIK, yang pertama kali dijelaskan oleh Barton dan Nackman dalam buku mereka * Scientific and Engineering C ++: An Introduction with Advanced Techniques and Example , di mana mereka memperkenalkan kelas yang disebut Fallowhasil fungsi "mungkin". Kelas serupa yang disebut optionalsekarang ditawarkan oleh perpustakaan Boost. Dan Anda dapat dengan mudah menerapkan Optionalkelas sendiri, menggunakan std::vectorsebagai pembawa nilai untuk kasus hasil non-POD.
Dengan pendekatan pertama kode pemanggil C tidak punya pilihan selain menggunakan teknik penanganan pengecualian. Dengan pendekatan kedua, bagaimanapun, kode panggilan C sendiri dapat memutuskan apakah akan melakukan ifpemeriksaan berbasis, atau penanganan pengecualian umum. Dengan demikian, pendekatan kedua mendukung pembuatan programmer versus trade-off efisiensi waktu eksekusi.
“Saya ingin tahu apakah ini masih berlaku untuk C ++ 98”
C ++ 98 adalah standar C ++ pertama. Untuk pengecualian itu memperkenalkan hierarki standar kelas pengecualian (sayangnya agak tidak sempurna). Dampak utama pada kinerja adalah kemungkinan spesifikasi pengecualian (dihapus dalam C ++ 11), yang tidak pernah sepenuhnya diimplementasikan oleh kompiler Windows C ++ utama Visual C ++: Visual C ++ menerima sintaks spesifikasi pengecualian C ++ 98, tetapi mengabaikan saja spesifikasi pengecualian.
C ++ 03 hanyalah korrigendum teknis dari C ++ 98. Satu-satunya yang benar-benar baru di C ++ 03 adalah inisialisasi nilai . Yang tidak ada hubungannya dengan pengecualian.
Dengan spesifikasi pengecualian umum standar C ++ 11 telah dihapus, dan diganti dengan noexceptkata kunci.
Standar C ++ 11 juga menambahkan dukungan untuk menyimpan dan menampilkan kembali pengecualian, yang sangat bagus untuk menyebarkan pengecualian C ++ di seluruh callback bahasa C. Dukungan ini secara efektif membatasi bagaimana pengecualian saat ini dapat disimpan. Namun, sejauh yang saya tahu itu tidak berdampak pada kinerja, kecuali sejauh penanganan pengecualian kode yang lebih baru dapat lebih mudah digunakan di kedua sisi panggilan balik bahasa C.
longjmpuntuk penangan.
try..finallymembangun dapat dilaksanakan tanpa unwinding stack. F #, C # dan Java semuanya diimplementasikan try..finallytanpa menggunakan stack unwinding. Anda hanya longjmpuntuk pawang (seperti yang sudah saya jelaskan).
Anda tidak pernah dapat mengklaim tentang kinerja kecuali Anda mengubah kode ke perakitan atau tolok ukurnya.
Inilah yang Anda lihat: (quick-bench)
Kode kesalahan tidak sensitif terhadap persentase kejadian. Pengecualian memiliki sedikit biaya selama tidak pernah dilemparkan. Begitu Anda membuangnya, kesengsaraan dimulai. Dalam contoh ini, itu dilemparkan untuk 0%, 1%, 10%, 50% dan 90% dari kasus. Ketika pengecualian dilemparkan 90% dari waktu, kode tersebut 8 kali lebih lambat daripada kasus di mana pengecualian dilemparkan 10% dari waktu. Seperti yang Anda lihat, pengecualiannya sangat lambat. Jangan gunakan jika sering dibuang. Jika aplikasi Anda tidak memiliki persyaratan real-time, silakan membuangnya jika sangat jarang terjadi.
Anda melihat banyak pendapat yang kontradiktif tentang mereka. Tapi akhirnya, apakah pengecualian itu lambat? Saya tidak menilai. Lihat saja patokannya.
Itu tergantung pada kompilernya.
GCC, misalnya, dikenal memiliki kinerja yang sangat buruk saat menangani pengecualian, tetapi ini menjadi jauh lebih baik dalam beberapa tahun terakhir.
Tetapi perhatikan bahwa menangani pengecualian harus - seperti namanya - menjadi pengecualian daripada aturan dalam desain perangkat lunak Anda. Jika Anda memiliki aplikasi yang menampilkan begitu banyak pengecualian per detik sehingga memengaruhi kinerja dan ini masih dianggap operasi normal, Anda sebaiknya berpikir untuk melakukan sesuatu secara berbeda.
Pengecualian adalah cara yang bagus untuk membuat kode lebih mudah dibaca dengan menyingkirkan semua kode penanganan kesalahan yang kikuk itu, tetapi begitu mereka menjadi bagian dari alur program normal, mereka menjadi sangat sulit untuk diikuti. Ingatlah bahwa a throwcukup banyak yang goto catchmenyamar.
throw new Exceptionadalah Java-isme. orang seharusnya tidak pernah melempar petunjuk.
Ya, tapi itu tidak masalah. Mengapa?
Baca ini:
https://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx
Pada dasarnya yang mengatakan bahwa menggunakan pengecualian seperti yang dijelaskan Alexandrescu (perlambatan 50x karena digunakan catchsebagai else) adalah salah. Itu dikatakan untuk ppl yang suka melakukannya seperti itu saya berharap C ++ 22 :) akan menambahkan sesuatu seperti:
(perhatikan ini harus menjadi bahasa inti karena pada dasarnya ini adalah kompiler yang menghasilkan kode dari yang sudah ada)
result = attempt<lexical_cast<int>>("12345"); //lexical_cast is boost function, 'attempt'
//... is the language construct that pretty much generates function from lexical_cast, generated function is the same as the original one except that fact that throws are replaced by return(and exception type that was in place of the return is placed in a result, but NO exception is thrown)...
//... By default std::exception is replaced, ofc precise configuration is possible
if (result)
{
int x = result.get(); // or result.result;
}
else
{
// even possible to see what is the exception that would have happened in original function
switch (result.exception_type())
//...
}
PS juga mencatat bahwa meskipun pengecualiannya lambat ... tidak masalah jika Anda tidak menghabiskan banyak waktu di bagian kode itu selama eksekusi ... Misalnya jika divisi float lambat dan Anda membuatnya 4x lebih cepat tidak masalah jika Anda menghabiskan 0,3% dari waktu Anda melakukan divisi FP ...
Seperti kata in silico, implementasinya bergantung, tetapi secara umum pengecualian dianggap lambat untuk implementasi apa pun dan tidak boleh digunakan dalam kode intensif performa.
EDIT: Saya tidak mengatakan tidak menggunakannya sama sekali tetapi untuk kode intensif kinerja yang terbaik adalah menghindarinya.