Jadi, saya tahu bahwa coba / tangkap memang menambahkan beberapa overhead dan oleh karena itu bukan cara yang baik untuk mengontrol aliran proses, tetapi dari mana datangnya overhead ini dan apa dampak sebenarnya?
Jawaban:
Saya bukan ahli dalam implementasi bahasa (jadi ambillah ini dengan sedikit garam), tapi saya pikir salah satu biaya terbesar adalah melepas tumpukan dan menyimpannya untuk jejak tumpukan. Saya menduga ini hanya terjadi ketika pengecualian dilemparkan (tetapi saya tidak tahu), dan jika demikian, ini akan menjadi biaya tersembunyi berukuran layak setiap kali pengecualian dilemparkan ... jadi tidak seperti Anda hanya melompat dari satu tempat dalam kode ke kode lain, ada banyak hal yang terjadi.
Saya tidak berpikir itu masalah selama Anda menggunakan pengecualian untuk perilaku LUAR BIASA (jadi bukan jalur yang biasa Anda harapkan melalui program).
Tiga poin yang harus dibuat di sini:
Pertama, ada sedikit atau TIDAK ada penalti kinerja dalam benar-benar memiliki blok coba-tangkap dalam kode Anda. Ini seharusnya tidak menjadi pertimbangan ketika mencoba untuk menghindari mereka dalam aplikasi Anda. Hit kinerja hanya mulai berlaku jika pengecualian dilemparkan.
Ketika pengecualian dilemparkan selain operasi pelepasan tumpukan dll yang terjadi yang telah disebutkan orang lain, Anda harus menyadari bahwa sejumlah besar hal-hal yang terkait dengan runtime / refleksi terjadi untuk mengisi anggota kelas pengecualian seperti pelacakan tumpukan objek dan berbagai tipe anggota dll.
Saya percaya bahwa ini adalah salah satu alasan mengapa saran umum jika Anda akan memunculkan kembali pengecualian adalah hanya throw;
daripada membuang pengecualian lagi atau membuat yang baru karena dalam kasus-kasus itu semua informasi tumpukan itu dikumpulkan kembali sedangkan dalam sederhana membuang itu semua diawetkan.
throw new Exception("Wrapping layer’s error", ex);
Apakah Anda bertanya tentang overhead menggunakan coba / tangkap / akhirnya ketika pengecualian tidak dilemparkan, atau overhead menggunakan pengecualian untuk mengontrol aliran proses? Yang terakhir ini agak mirip dengan menggunakan dinamit untuk menyalakan lilin ulang tahun balita, dan overhead yang terkait jatuh ke dalam area berikut:
Anda dapat mengharapkan kesalahan halaman tambahan karena pengecualian yang diberikan mengakses kode non-residen dan data biasanya tidak ada dalam set kerja aplikasi Anda.
kedua item di atas biasanya mengakses kode dan data "dingin", jadi kesalahan halaman keras mungkin terjadi jika Anda memiliki tekanan memori sama sekali:
Adapun dampak biaya yang sebenarnya, ini dapat sangat bervariasi tergantung pada apa lagi yang terjadi dalam kode Anda saat itu. Jon Skeet memiliki ringkasan yang bagus di sini , dengan beberapa tautan berguna. Saya cenderung setuju dengan pernyataannya bahwa jika Anda sampai pada titik di mana pengecualian secara signifikan mengganggu kinerja Anda, Anda memiliki masalah dalam hal penggunaan pengecualian di luar kinerja.
Dalam pengalaman saya, overhead terbesar sebenarnya adalah melakukan pengecualian dan menanganinya. Saya pernah mengerjakan proyek di mana kode yang mirip dengan berikut ini digunakan untuk memeriksa apakah seseorang memiliki hak untuk mengedit beberapa objek. Metode HasRight () ini digunakan di mana saja di lapisan presentasi, dan sering dipanggil untuk 100 objek.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Ketika database pengujian menjadi lebih penuh dengan data pengujian, ini menyebabkan perlambatan yang sangat terlihat saat membuka formulir baru, dll.
Jadi saya memfaktorkan ulangnya menjadi yang berikut, yang - menurut pengukuran cepat dan kotor - sekitar 2 kali lipat lebih cepat:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Jadi singkatnya, menggunakan pengecualian dalam aliran proses normal adalah sekitar dua kali lipat lebih lambat daripada menggunakan aliran proses serupa tanpa pengecualian.
Bertentangan dengan teori yang diterima secara umum, try
/ catch
dapat memiliki implikasi kinerja yang signifikan, dan terlepas dari apakah pengecualian dilemparkan atau tidak!
Yang pertama telah tercakup dalam beberapa posting blog oleh Microsoft MVP selama bertahun-tahun, dan saya percaya Anda dapat menemukannya dengan mudah namun StackOverflow sangat peduli dengan konten jadi saya akan memberikan tautan ke beberapa di antaranya sebagai bukti pengisi :
try
/ catch
/finally
( dan bagian dua ), oleh Peter Ritchie mengeksplorasi pengoptimalan yangtry
/catch
/finally
menonaktifkan (dan saya akan membahas lebih jauh ini dengan kutipan dari standar)Parse
vs. TryParse
vs.ConvertTo
oleh Ian Huff menyatakan secara terang-terangan bahwa "penanganan pengecualian sangat lambat" dan menunjukkan hal ini dengan mengadu dombaInt.Parse
danInt.TryParse
melawan satu sama lain ... Bagi siapa pun yang bersikeras bahwaTryParse
menggunakantry
/ dicatch
belakang layar, ini harus menjelaskan!Ada juga jawaban ini yang menunjukkan perbedaan antara kode yang dibongkar dengan- dan tanpa menggunakan try
/catch
.
Tampaknya begitu jelas bahwa ada adalah overhead yang terang-terangan diamati dalam pembuatan kode, dan overhead yang bahkan tampaknya akan diakui oleh orang-orang yang nilai Microsoft! Namun saya, mengulangi internet ...
Ya, ada lusinan instruksi MSIL tambahan untuk satu baris kode yang sepele, dan itu bahkan tidak mencakup pengoptimalan yang dinonaktifkan jadi secara teknis ini adalah pengoptimalan mikro.
Saya memposting jawaban bertahun-tahun yang lalu yang dihapus karena berfokus pada produktivitas pemrogram (pengoptimalan makro).
Hal ini sangat disayangkan karena tidak adanya penghematan beberapa nanodetik di sana-sini waktu CPU kemungkinan akan menutupi banyak jam akumulasi pengoptimalan manual oleh manusia. Mana yang dibayar bos Anda lebih: satu jam waktu Anda, atau satu jam dengan komputer berjalan? Pada titik manakah kita menarik steker dan mengakui bahwa sudah waktunya untuk membeli komputer yang lebih cepat ?
Jelas, prioritas kita harus dioptimalkan , bukan hanya kode kita! Dalam jawaban terakhir saya, saya memanfaatkan perbedaan antara dua potongan kode.
Menggunakan try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Tidak menggunakan try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Pertimbangkan dari perspektif pengembang pemeliharaan, yang lebih cenderung membuang-buang waktu Anda, jika tidak dalam pembuatan profil / pengoptimalan (dibahas di atas) yang mungkin tidak akan diperlukan jika bukan karena try
/ catch
masalah, kemudian dalam menggulir kode sumber ... Salah satunya memiliki empat baris tambahan sampah boilerplate!
Karena semakin banyak kolom yang diperkenalkan ke dalam sebuah kelas, semua sampah boilerplate ini terakumulasi (baik dalam kode sumber maupun kode yang dibongkar) jauh melampaui level yang wajar. Empat baris tambahan per bidang, dan baris itu selalu sama ... Bukankah kita diajari untuk menghindari pengulangan diri kita sendiri? Saya kira kita bisa menyembunyikan try
/ di catch
belakang beberapa abstraksi buatan sendiri, tapi ... maka kita sebaiknya menghindari pengecualian (yaitu menggunakanInt.TryParse
).
Ini bahkan bukan contoh yang rumit; Saya telah melihat upaya untuk membuat contoh kelas baru di try
/ catch
. Pertimbangkan bahwa semua kode di dalam konstruktor mungkin kemudian didiskualifikasi dari pengoptimalan tertentu yang sebaliknya akan diterapkan secara otomatis oleh kompilator. Cara apa yang lebih baik untuk memunculkan teori bahwa kompilator lambat , dibandingkan dengan kompilator adalah melakukan persis seperti yang diperintahkan ?
Dengan asumsi pengecualian dilemparkan oleh konstruktor tersebut, dan beberapa bug dipicu sebagai hasilnya, pengembang pemeliharaan yang buruk kemudian harus melacaknya. Itu mungkin bukan tugas yang mudah, tidak seperti kode spageti dari mimpi buruk goto , try
/ catch
dapat menyebabkan kekacauan dalam tiga dimensi , karena dapat meningkatkan tumpukan tidak hanya ke bagian lain dari metode yang sama, tetapi juga kelas dan metode lain , yang semuanya akan diamati oleh pengembang pemeliharaan, dengan cara yang sulit ! Namun kami diberitahu bahwa "pergi itu berbahaya", heh!
Di akhir saya menyebutkan, try
/ catch
memiliki manfaatnya yaitu, ini dirancang untuk menonaktifkan pengoptimalan ! Ini, jika Anda mau, bantuan debugging ! Untuk itulah ia dirancang dan untuk itulah ia harus digunakan sebagai ...
Saya rasa itu juga poin positif. Ini dapat digunakan untuk menonaktifkan pengoptimalan yang mungkin dapat melumpuhkan algoritme penyampaian pesan yang aman dan waras untuk aplikasi multithread, dan untuk menangkap kemungkinan kondisi balapan;) Itulah satu-satunya skenario yang dapat saya pikirkan untuk menggunakan coba / tangkap. Bahkan itu punya alternatif.
Pengoptimalan apa try
, catch
dan finally
nonaktifkan?
AKA
Bagaimana kabarnya try
, catch
dan finally
berguna sebagai alat bantu debugging?
mereka menulis-hambatan. Ini berasal dari standar:
12.3.3.13 Pernyataan coba-tangkap
Untuk pernyataan stmt dalam bentuk:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- Keadaan penugasan pasti v di awal blok percobaan sama dengan status penugasan pasti v di awal stmt .
- Keadaan penugasan pasti v di awal catch-block-i (untuk sembarang i ) sama dengan status penetapan pasti v di awal stmt .
- Keadaan penugasan pasti v pada titik akhir stmt pasti ditetapkan jika (dan hanya jika) v pasti ditetapkan pada titik akhir blok percobaan dan setiap blok-tangkap-i (untuk setiap i dari 1 hingga n ).
Dengan kata lain, di awal masing-masing try
pernyataan:
try
pernyataan harus diselesaikan, yang memerlukan kunci utas sebagai permulaan, sehingga berguna untuk men-debug kondisi balapan!try
pernyataanCerita serupa berlaku untuk setiap catch
pernyataan; misalkan dalam try
pernyataan Anda (atau konstruktor atau fungsi yang dipanggil, dll.) Anda tetapkan ke variabel yang tidak berguna itu (katakanlah, garbage=42;
), kompilator tidak dapat menghilangkan pernyataan itu, tidak peduli betapa tidak relevannya itu dengan perilaku program yang dapat diamati . Tugas harus diselesaikan sebelumcatch
blok dimasukkan.
Untuk apa nilainya, finally
menceritakan sama merendahkan kisah yang :
12.3.3.14 Pernyataan coba-akhirnya
Untuk mencoba pernyataan stmt dalam bentuk:
try try-block finally finally-block
• Status tugas pasti v di awal blok percobaan sama dengan status penetapan pasti v di awal stmt .
• Status tugas pasti v di awal blok akhir sama dengan status tugas pasti v di awal stmt .
• Status penetapan pasti v pada titik akhir stmt pasti ditetapkan jika (dan hanya jika) salah satu: o v pasti ditetapkan pada titik akhir blok percobaan o vpasti ditugaskan di titik akhir blok terakhir Jika transfer aliran kontrol (seperti pernyataan goto ) dibuat yang dimulai dalam blok percobaan , dan berakhir di luar blok percobaan , maka v juga dianggap pasti ditugaskan pada itu transfer aliran kontrol jika v pasti ditetapkan pada titik akhir blok akhir . (Ini bukan hanya jika — jika v pasti ditetapkan karena alasan lain pada transfer aliran kontrol ini, maka itu masih dianggap pasti ditetapkan.)
12.3.3.15 Pernyataan coba-tangkap-akhirnya
Analisis tugas pasti untuk pernyataan try - catch - akhirnya dalam bentuk:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
dilakukan seolah-olah pernyataan itu adalah percobaan - akhirnya pernyataan yang menyertakan pernyataan coba - tangkap :
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Belum lagi jika itu di dalam metode yang sering disebut itu dapat mempengaruhi perilaku aplikasi secara keseluruhan.
Sebagai contoh, saya menganggap penggunaan Int32.Parse sebagai praktik yang buruk dalam banyak kasus karena ia memberikan pengecualian untuk sesuatu yang dapat ditangkap dengan mudah jika tidak.
Jadi untuk menyimpulkan semua yang tertulis di sini:
1) Gunakan blok try..catch untuk menangkap kesalahan tak terduga - hampir tidak ada penalti kinerja.
2) Jangan gunakan pengecualian untuk kesalahan yang dikecualikan jika Anda dapat menghindarinya.
Saya menulis artikel tentang ini beberapa waktu yang lalu karena ada banyak orang yang menanyakan hal ini pada saat itu. Anda dapat menemukannya dan kode uji di http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx .
Hasilnya adalah bahwa ada sejumlah kecil overhead untuk blok coba / tangkap tetapi sangat kecil sehingga harus diabaikan. Namun, jika Anda menjalankan blok coba / tangkap dalam loop yang dieksekusi jutaan kali, Anda mungkin ingin mempertimbangkan untuk memindahkan blok ke luar loop jika memungkinkan.
Masalah kinerja utama dengan blok coba / tangkap adalah ketika Anda benar-benar menangkap pengecualian. Ini dapat menambah penundaan yang nyata pada aplikasi Anda. Tentu saja, ketika ada yang tidak beres, sebagian besar pengembang (dan banyak pengguna) mengenali jeda sebagai pengecualian yang akan terjadi! Kuncinya di sini bukanlah menggunakan penanganan pengecualian untuk operasi normal. Seperti namanya, mereka luar biasa dan Anda harus melakukan apa saja untuk menghindari mereka terlempar. Anda tidak boleh menggunakannya sebagai bagian dari aliran yang diharapkan dari program yang berfungsi dengan benar.
Saya membuat entri blog tentang subjek ini tahun lalu. Saksikan berikut ini. Intinya adalah bahwa hampir tidak ada biaya untuk blok percobaan jika tidak ada pengecualian - dan di laptop saya, pengecualian adalah sekitar 36μs. Itu mungkin kurang dari yang Anda harapkan, tetapi perlu diingat bahwa hasil tersebut berada di tumpukan yang dangkal. Juga, pengecualian pertama sangat lambat.
try
/ catch
terlalu banyak? Heh heh), tetapi Anda tampaknya berdebat dengan spesifikasi bahasa dan beberapa MS MVP yang juga menulis blog tentang subjek tersebut, memberikan pengukuran bertentangan dengan saran Anda ... Saya terbuka untuk saran bahwa penelitian yang telah saya lakukan salah, tetapi saya perlu membaca entri blog Anda untuk melihat apa yang dikatakan.
try-catch
blok vs tryparse()
, tetapi konsepnya sama.
Jauh lebih mudah untuk menulis, men-debug, dan memelihara kode yang bebas dari pesan kesalahan compiler, pesan peringatan analisis kode, dan pengecualian rutin yang diterima (terutama pengecualian yang dilemparkan di satu tempat dan diterima di tempat lain). Karena lebih mudah, kode rata-rata akan lebih baik ditulis dan lebih sedikit buggy.
Bagi saya, programmer dan overhead kualitas itu adalah argumen utama yang menentang penggunaan try-catch untuk aliran proses.
Pengecualian overhead komputer tidak signifikan jika dibandingkan, dan biasanya kecil dalam hal kemampuan aplikasi untuk memenuhi persyaratan kinerja dunia nyata.
Saya sangat suka postingan blog Hafthor , dan untuk menambahkan dua sen saya ke diskusi ini, saya ingin mengatakan bahwa, selalu mudah bagi saya untuk membuat LAPISAN DATA hanya melemparkan satu jenis pengecualian (DataAccessException). Dengan cara ini LAPISAN BISNIS saya tahu pengecualian apa yang diharapkan dan menangkapnya. Kemudian bergantung pada aturan bisnis lebih lanjut (yaitu jika objek bisnis saya berpartisipasi dalam alur kerja, dll.), Saya dapat membuat pengecualian baru (BusinessObjectException) atau melanjutkan tanpa mengulang / membuang.
Saya akan mengatakan jangan ragu untuk menggunakan try..catch kapan pun diperlukan dan gunakan dengan bijak!
Misalnya, metode ini berpartisipasi dalam alur kerja ...
Komentar?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
Kita dapat membaca di Programming Languages Pragmatics oleh Michael L. Scott bahwa kompiler saat ini tidak menambahkan overhead apapun dalam kasus umum, ini berarti, ketika tidak ada pengecualian. Jadi setiap pekerjaan dibuat dalam waktu kompilasi. Tetapi ketika pengecualian dilemparkan pada waktu proses, kompilator perlu melakukan pencarian biner untuk menemukan pengecualian yang benar dan ini akan terjadi untuk setiap lemparan baru yang Anda buat.
Tetapi pengecualian adalah pengecualian dan biaya ini dapat diterima. Jika Anda mencoba melakukan Penanganan Pengecualian tanpa pengecualian dan menggunakan kode kesalahan pengembalian, mungkin Anda akan memerlukan pernyataan if untuk setiap subrutin dan ini akan menimbulkan overhead waktu nyata. Anda tahu jika pernyataan diubah menjadi beberapa instruksi perakitan, yang akan dilakukan setiap kali Anda memasukkan sub-rutin Anda.
Maaf tentang bahasa Inggris saya, semoga dapat membantu Anda. Informasi ini berdasarkan pada buku yang dikutip, untuk informasi lebih lanjut lihat Bab 8.5 Penanganan Pengecualian.
Mari kita analisis salah satu kemungkinan biaya terbesar dari blok coba / tangkap saat digunakan di tempat yang seharusnya tidak perlu digunakan:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Dan inilah yang tanpa coba / tangkap:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Tidak menghitung white-space yang tidak signifikan, orang mungkin memperhatikan bahwa kedua potongan kode yang sama ini hampir sama panjangnya dalam byte. Yang terakhir berisi lekukan 4 byte lebih sedikit. Itu adalah hal yang buruk?
Untuk menambahkan penghinaan pada cedera, seorang siswa memutuskan untuk mengulang sementara input dapat diuraikan sebagai int. Solusi tanpa coba / tangkap mungkin seperti ini:
while (int.TryParse(...))
{
...
}
Tapi bagaimana tampilannya saat menggunakan try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Blok coba / tangkap adalah cara ajaib untuk membuang-buang lekukan, dan kita masih belum tahu alasan kegagalannya! Bayangkan bagaimana perasaan orang yang melakukan debugging, ketika kode terus dieksekusi melewati cacat logis yang serius, daripada berhenti dengan kesalahan pengecualian yang jelas dan bagus. Blok coba / tangkap adalah validasi / sanitasi data orang malas.
Salah satu biaya yang lebih kecil adalah bahwa blok coba / tangkap memang menonaktifkan pengoptimalan tertentu: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implications-of-try-catch-finally.aspx . Saya rasa itu juga poin positif. Ini dapat digunakan untuk menonaktifkan pengoptimalan yang mungkin dapat melumpuhkan algoritma penyampaian pesan yang aman dan waras untuk aplikasi multithread, dan untuk menangkap kemungkinan kondisi balapan;) Itulah satu-satunya skenario yang dapat saya pikirkan untuk menggunakan coba / tangkap. Bahkan itu punya alternatif.
Int.Parse
mendukung Int.TryParse
.