Apakah Pengecualian di C ++ sangat lambat


99

Saya menonton Systematic Error Handling di C ++ - Andrei Alexandrescu dia mengklaim bahwa Pengecualian di C ++ sangat lambat.

Apakah ini masih berlaku untuk C ++ 98?


43
Tidak masuk akal untuk menanyakan apakah "pengecualian C ++ 98" lebih cepat / lebih lambat daripada "pengecualian C ++ 03" atau "pengecualian C ++ 11". Performanya bergantung pada bagaimana kompilator mengimplementasikannya dalam program Anda, dan standar C ++ tidak mengatakan apa-apa tentang bagaimana mereka harus diimplementasikan; satu-satunya persyaratan adalah bahwa perilaku mereka harus mengikuti standar (aturan "seolah-olah").
In silico

Terkait (tetapi tidak benar-benar duplikat) pertanyaan: stackoverflow.com/questions/691168/…
Filipi

2
ya, ini sangat lambat, tetapi mereka tidak boleh dilemparkan untuk operasi normal atau digunakan sebagai cabang
BЈовић

Saya telah menemukan pertanyaan serupa .
PaperBirdMaster

Untuk memperjelas apa yang BЈовић katakan, menggunakan pengecualian bukanlah sesuatu yang perlu ditakuti. Ketika pengecualian dilemparkan, Anda menghadapi (berpotensi) operasi yang memakan waktu. Saya juga ingin tahu mengapa Anda ingin tahu untuk C ++ 89 secara khusus ... bahwa versi terbaru adalah C ++ 11, dan waktu yang diperlukan untuk menjalankan pengecualian adalah implementasi yang ditentukan, oleh karena itu 'berpotensi' saya memakan waktu .
thecoshman

Jawaban:


162

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 :

  • model Zero-Cost, seperti namanya, gratis jika tidak ada pengecualian
  • biayanya sekitar 10x / 20x ifketika pengecualian terjadi

Namun, biayanya tidak sepele untuk diukur:

  • Meja samping biasanya dingin , sehingga mengambilnya dari memori membutuhkan waktu lama
  • Menentukan handler kanan melibatkan RTTI: banyak deskriptor RTTI untuk diambil, tersebar di sekitar memori, dan operasi kompleks untuk dijalankan (pada dasarnya adalah 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.


3
+1 Saya hanya akan menambahkan empat hal: (0) tentang dukungan untuk penggambaran ulang yang ditambahkan dalam C ++ 11; (1) referensi atas laporan komite efisiensi c ++; (2) beberapa komentar tentang kebenaran (bahkan melebihi keterbacaan); dan (3) tentang kinerja, komentar tentang mengukurnya terhadap kasus tidak menggunakan pengecualian (semua relatif)
Ceria dan hth. - Alf

2
@ Cheersandhth.-Alf: (0), (1) dan (3) selesai: terima kasih. Mengenai kebenaran (2), sementara itu mengalahkan keterbacaan, saya tidak yakin tentang pengecualian yang mengarah ke kode yang lebih benar daripada strategi penanganan kesalahan lainnya (sangat mudah untuk melupakan tentang banyak jalur tak terlihat dari pengecualian eksekusi yang dibuat).
Matthieu M.

2
Deskripsi mungkin benar secara lokal, tetapi perlu diperhatikan bahwa keberadaan pengecualian memiliki implikasi global pada asumsi dan pengoptimalan yang dapat dilakukan oleh compiler. Implikasi ini mengalami masalah karena mereka "tidak memiliki contoh tandingan yang sepele", karena kompilator selalu dapat melihat melalui program kecil. Membuat profil pada basis kode besar yang realistis dengan dan tanpa pengecualian mungkin merupakan ide yang bagus.
Kerrek SB

4
> Model Zero-Cost, seperti yang tersirat dari namanya, adalah gratis jika tidak ada pengecualian, hal ini sebenarnya tidak benar sampai ke tingkat detail terbaik. menghasilkan lebih banyak kode selalu berdampak pada kinerja, bahkan jika kecil dan halus ... mungkin perlu sedikit lebih lama bagi OS untuk memuat file yang dapat dieksekusi, atau Anda akan mendapatkan lebih banyak i-cache miss. juga, bagaimana dengan kode pelepasan tumpukan? juga, bagaimana dengan eksperimen yang dapat Anda lakukan untuk mengukur efek daripada mencoba memahaminya dengan pemikiran rasional?
jheriko

2
@ Jheriko: Saya yakin saya sudah menjawab sebagian besar pertanyaan Anda, sebenarnya. Waktu untuk memuat tidak boleh terpengaruh (kode dingin tidak boleh dimuat), i-cache tidak boleh terpengaruh (kode dingin tidak boleh masuk ke i-cache), ... jadi untuk menjawab satu pertanyaan yang hilang: "how to measure" => mengganti pengecualian apa pun yang muncul dengan panggilan ke 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...
Matthieu M.

60

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.


Apakah pengecualian sangat lambat di C ++, dibandingkan dengan bahasa lain?

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.


Apakah ada ukuran objektif dari performa pelemparan pengecualian C ++?

Ya, komite standardisasi C ++ internasional telah menerbitkan Laporan Teknis tentang kinerja C ++, TR18015 .


Apa artinya pengecualian itu "lambat"?

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 .


Apa cara yang baik untuk menghindari overhead pengecualian?

Ketepatan hampir selalu mengalahkan efisiensi.

Tanpa pengecualian, hal berikut dapat dengan mudah terjadi:

  1. Beberapa kode P dimaksudkan untuk mendapatkan sumber daya atau menghitung beberapa informasi.

  2. Kode panggilan C seharusnya telah memeriksa keberhasilan / kegagalan, tetapi tidak.

  3. 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.


Apa dampak dari berbagai standar C ++, pada kinerja pengecualian?

“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.


6
"pengecualian selalu lambat dibandingkan dengan operasi dasar lainnya dalam bahasa tersebut, apa pun bahasa pemrogramannya" ... kecuali dalam bahasa yang dirancang untuk mengompilasi penggunaan pengecualian ke dalam kontrol aliran biasa.
Ben Voigt

4
"melempar pengecualian melibatkan alokasi dan pelepasan tumpukan". Itu juga jelas tidak benar secara umum dan, sekali lagi, OCaml adalah contoh tandingan. Dalam bahasa yang dikumpulkan sampah tidak perlu melepas tumpukan karena tidak ada penghancur, jadi Anda hanya longjmpuntuk penangan.
JD

2
@JonHarrop: mungkin Anda tidak menyadari bahwa Pyhon memiliki klausa akhir untuk penanganan pengecualian. ini berarti implementasi Python memiliki stack unwinding atau bukan Python. Anda tampaknya benar-benar tidak peduli tentang subjek yang Anda klaim (fantasi). Maaf.
Cheers and hth. - Alf

2
@ Cheersandhth.-Alf: "Pyhon memiliki klausa akhir untuk penanganan pengecualian. Ini berarti implementasi Python memiliki stack unwinding atau bukan Python". The 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).
JD

4
@JonHarrop: Anda terdengar seperti membuat dilema. tetapi tidak ada relevansinya saya dapat melihat apa pun yang dibahas sejauh ini, dan sejauh ini Anda telah memposting urutan panjang omong kosong yang terdengar negatif . saya harus mempercayai Anda untuk setuju atau tidak dengan beberapa kata yang tidak jelas, karena sebagai antagonis Anda memilih apa yang akan Anda ungkapkan bahwa itu "berarti", dan saya pasti tidak mempercayai Anda setelah semua omong kosong yang tidak berarti, downvoting dll.
Cheers and hth. - Alf

13

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.

Tolok ukur performa pengecualian C ++


12

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.


-1 adalah pertanyaan seperti sekarang, "apakah ini masih berlaku untuk C ++ 98", yang tentunya tidak bergantung pada kompiler. juga, jawaban ini throw new Exceptionadalah Java-isme. orang seharusnya tidak pernah melempar petunjuk.
Cheers and hth. - Alf

1
apakah standar 98 menentukan dengan tepat bagaimana pengecualian akan diterapkan?
thecoshman

6
C ++ 98 adalah standar ISO, bukan kompiler. Ada banyak kompiler yang mengimplementasikannya.
Philipp

3
@thecoshman: Tidak. Standar C ++ tidak menjelaskan apa pun tentang bagaimana sesuatu harus diimplementasikan (dengan kemungkinan pengecualian bagian "Batas Implementasi" dari standar).
In silico

2
@Insilico maka saya hanya dapat menarik kesimpulan logis bahwa (yang mengejutkan) itu adalah implementasi yang ditentukan (baca, spesifik kompilator) bagaimana kinerja pengecualian.
thecoshman

4

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 ...


0

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.


9
Ini paling-paling cara yang sangat sederhana untuk melihat kinerja pengecualian. Misalnya, GCC menggunakan penerapan "tanpa biaya" di mana Anda tidak menimbulkan klik kinerja jika tidak ada pengecualian. Dan pengecualian dimaksudkan untuk keadaan luar biasa (yaitu jarang), jadi meskipun keadaan itu lambat oleh beberapa metrik, itu masih belum cukup alasan untuk tidak menggunakannya.
In silico

@insilico jika Anda melihat mengapa saya berkata, saya tidak mengatakan untuk tidak menggunakan pengecualian titik penuh. Saya menentukan kode intensif kinerja, ini adalah penilaian yang akurat, saya terutama bekerja dengan gpgpus dan saya akan ditembak jika saya menggunakan pengecualian.
Chris McCabe
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.