Coba-akhirnya mahal

14

Dalam hal kode di mana Anda harus melakukan pembersihan sumber daya sebelum keluar dari suatu fungsi, apakah ada perbedaan kinerja utama antara 2 cara melakukannya.

  1. Membersihkan sumber daya sebelum setiap pernyataan pengembalian

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Membersihkan sumber daya di blok akhirnya

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

Saya melakukan beberapa tes dasar dalam program sampel dan sepertinya tidak ada banyak perbedaan. Saya jauh lebih suka finallycara melakukan ini - tapi saya bertanya-tanya apakah itu akan menyebabkan kinerja apa pun dalam proyek besar.

pengguna93353
sumber
3
Ini tidak benar-benar layak dikirim sebagai jawaban, tetapi tidak. Jika Anda menggunakan Java, yang mengharapkan pengecualian sebagai strategi penanganan kesalahan default, maka Anda harus menggunakan try / catch / akhirnya - bahasa dioptimalkan untuk pola yang Anda gunakan.
Jonathan Rich
1
@ JonathanRich: bagaimana bisa begitu?
haylem
2
Tulis kode dengan bersih sedemikian rupa sehingga menyatakan apa yang ingin Anda capai. Optimalkan nanti ketika Anda tahu Anda memiliki masalah - jangan menghindari fitur bahasa karena Anda berpikir bahwa Anda mungkin mencukur beberapa milidetik dari sesuatu yang tidak lambat pada awalnya.
Sean McSomething
@ MikeTheLiar - Jika Anda maksud saya harus menulis if(!cond), maka itu java yang membuat saya melakukan ini. Dalam C ++, begitulah cara saya menulis kode boolean dan juga untuk jenis lainnya - yaitu int x; if(!x). Karena java memungkinkan saya untuk menggunakan ini hanya untuk booleans, saya benar-benar berhenti menggunakan if(cond)& if(!cond)di java.
user93353
1
@ user93353 yang membuat saya sedih. Saya lebih suka melihat (someIntValue != 0)daripada membandingkan daripada mengevaluasi boolean. Itu bau bagi saya, dan saya segera refactor ketika saya melihatnya di alam liar.
MikeTheLiar

Jawaban:

23

Seperti ditunjukkan dalam Seberapa lambat pengecualian Java? orang dapat melihat bahwa kelambatan try {} catch {}dalam instatiation pengecualian itu sendiri.

Membuat pengecualian akan mengambil seluruh tumpukan panggilan dari runtime dan di sinilah biayanya. Jika Anda tidak menciptakan pengecualian, ini hanya sangat sedikit peningkatan dari waktu ke waktu.

Dalam contoh yang diberikan dalam pertanyaan ini tidak ada pengecualian, orang tidak akan mengharapkan ada pelambatan untuk membuatnya - mereka tidak dibuat. Alih-alih, yang ada di sini adalah try {} finally {}menangani deallokasi sumber daya di dalam blok akhirnya.

Jadi untuk menjawab pertanyaan, tidak, tidak ada biaya runtime nyata dalam try {} finally {}struktur yang tidak menggunakan pengecualian (tidak pernah terdengar seperti yang terlihat). Apa yang mungkin mahal adalah waktu pemeliharaan ketika seseorang membaca kode dan melihat ini bukan gaya kode yang khas dan pembuat kode harus memikirkan bahwa sesuatu yang lain terjadi dalam metode ini setelah returnsebelum kembali ke panggilan sebelumnya.


Seperti yang telah disebutkan, pemeliharaan adalah argumen untuk kedua cara melakukan ini. Sebagai catatan, setelah mempertimbangkan, preferensi saya akan menjadi pendekatan terakhir.

Pertimbangkan waktu pemeliharaan untuk mengajar seseorang struktur bahasa baru. Melihat try {} finally {}bukanlah sesuatu yang sering dilihat dalam kode Java dan karenanya dapat membingungkan orang. Ada tingkat waktu pemeliharaan untuk mempelajari struktur yang sedikit lebih maju di Jawa daripada yang biasa dilihat orang.

The finally {}blok selalu berjalan. Dan inilah mengapa Anda harus menggunakannya. Pertimbangkan juga waktu pemeliharaan untuk debug pendekatan non-akhirnya ketika seseorang lupa untuk memasukkan logout pada waktu yang tepat, atau menyebutnya pada waktu yang tidak tepat, atau lupa untuk kembali / keluar setelah memanggilnya sehingga dipanggil dua kali. Ada begitu banyak kemungkinan bug dengan ini sehingga penggunaan try {} finally {}tidak memungkinkan untuk dimiliki.

Ketika menimbang kedua biaya ini, mahal dalam waktu perawatan untuk tidak menggunakan try {} finally {}pendekatan. Sementara orang dapat dicker tentang berapa banyak milidetik fraksional atau instruksi jvm tambahan try {} finally {}blok dibandingkan dengan versi lain, orang juga harus mempertimbangkan jam yang dihabiskan dalam debugging cara kurang ideal untuk menangani deallokasi sumber daya.

Tulis kode yang dapat dipelihara terlebih dahulu, dan lebih disukai dengan cara yang akan mencegah bug ditulis kemudian.

Komunitas
sumber
1
Sebuah edit anon-disarankan mengatakan demikian: "coba / akhirnya menawarkan jaminan Anda tidak akan memiliki sebaliknya mengenai pengecualian runtime. Apakah Anda suka atau tidak, baris apa pun sebenarnya mampu melempar pengecualian" - Saya: yang tidak sepenuhnya benar . Struktur yang diberikan dalam pertanyaan tidak memiliki pengecualian tambahan yang akan memperlambat kinerja kode. Tidak ada dampak kinerja tambahan dengan membungkus coba / akhirnya di sekitar blok dibandingkan dengan non-coba / akhirnya blok.
2
Apa yang bisa lebih mahal dalam pemeliharaan daripada memahami penggunaan coba-akhirnya ini, adalah harus memelihara beberapa blok pembersihan. Sekarang setidaknya Anda tahu bahwa jika diperlukan pembersihan tambahan, hanya ada satu tempat untuk meletakkannya.
Bart van Ingen Schenau
@BartvanIngenSchenau Memang. Coba-akhirnya mungkin adalah cara terbaik untuk menanganinya, itu hanya tidak biasa dari struktur yang saya lihat dalam kode - orang memiliki cukup kesulitan mencoba memahami akhirnya mencoba menangkap dan penanganan secara umum .. tetapi coba - Akhirnya tanpa tangkapan? Apa? Orang benar-benar tidak memahaminya . Seperti yang Anda sarankan, lebih baik untuk memahami struktur bahasa daripada mencoba menghindarinya dan melakukan hal-hal buruk.
Saya sebagian besar ingin memberi tahu bahwa alternatif untuk mencoba-akhirnya tidak gratis juga. Saya berharap saya bisa memberikan upvote lain untuk tambahan Anda tentang biaya.
Bart van Ingen Schenau
5

/programming/299068/how-slow-are-java-exceptions

Jawaban yang diterima pada pertanyaan ini menunjukkan membungkus panggilan fungsi dalam blok uji coba biaya kurang dari 5% dibandingkan panggilan fungsi telanjang. Sebenarnya melempar dan menangkap pengecualian menyebabkan runtime membengkak menjadi lebih dari 66x panggilan fungsi kosong. Jadi jika Anda mengharapkan desain pengecualian untuk melempar secara teratur maka saya akan mencoba menghindarinya (dalam kode kinerja kritis yang diprofilkan dengan benar), namun jika situasi luar biasa jarang maka itu bukan masalah besar.

stonemetal
sumber
3
"Jika situasi luar biasa jarang terjadi" - yah ada tautologi di sana. Luar biasa berarti langka dan itulah tepatnya pengecualian harus digunakan. Jangan pernah menjadi bagian dari aliran desain atau kontrol.
Jesse C. Slicer
1
Pengecualian tidak selalu jarang dalam praktek, itu hanya efek samping yang tidak disengaja. Misalnya, jika Anda memiliki UI yang buruk orang mungkin menyebabkan aliran kasus penggunaan pengecualian lebih sering daripada aliran normal
Esailija
2
Tidak ada pengecualian yang dimasukkan dalam kode pertanyaan.
2
@ JesseC.Slicer Ada beberapa bahasa yang tidak jarang melihatnya digunakan sebagai kontrol aliran. Sebagai contoh ketika iterating sesuatu dengan python iterator melempar pengecualian stopiteration ketika selesai. Juga di OCaml perpustakaan standar mereka tampaknya sangat "melempar bahagia", itu melempar dalam sejumlah besar situasi yang tidak jarang (jika Anda memiliki peta dan mencoba mencari sesuatu yang tidak ada di peta yang dilemparkannya) ).
stonemetal
@stonemetal Seperti halnya dict Python, dengan KeyError
Izkata
4

Alih-alih mengoptimalkan - pikirkan apa yang dilakukan kode Anda.

Menambahkan blok akhirnya adalah implementasi yang berbeda - itu berarti bahwa Anda akan keluar pada setiap pengecualian.

Khususnya - jika login melempar pengecualian, perilaku di fungsi kedua Anda akan berbeda dari yang pertama.

Jika masuk dan keluar sebenarnya bukan bagian dari perilaku fungsi, maka saya menyarankan agar metode ini melakukan satu hal dan satu hal dengan baik:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

dan harus berada dalam fungsi yang sudah dienkapsulasi dalam konteks yang mengelola login / logout karena ini tampaknya berbeda secara mendasar dari apa yang dilakukan kode utama Anda.

Posting Anda ditandai sebagai Java yang tidak terlalu saya kenal tetapi di .net saya akan menggunakan blok penggunaan untuk ini:

yaitu:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

Dan konteks login memiliki metode Buang yang akan keluar dan membersihkan sumber daya.

Sunting: Saya sebenarnya tidak menjawab pertanyaan!

Saya tidak memiliki bukti yang terukur tetapi saya tidak berharap ini menjadi lebih mahal atau tentu saja tidak cukup mahal untuk itu layak untuk optimasi prematur.

Saya akan meninggalkan sisa jawaban saya karena walaupun tidak menjawab pertanyaan, saya pikir ini membantu untuk OP.

Michael
sumber
1
Untuk menyelesaikan jawaban Anda, Java 7 memperkenalkan pernyataan coba-dengan-sumber daya yang akan mirip dengan usingkonstruksi .net Anda , dengan asumsi bahwa sumber daya yang dimaksud mengimplementasikan AutoCloseableantarmuka.
haylem
Saya bahkan akan menghapus retvariabel ini , yang hanya ditugaskan dua kali untuk diperiksa satu baris nanti. Buat itu if (dosomething() == false) return;.
ott--
Setuju tetapi saya ingin menjaga kode OP seperti yang disediakan.
Michael
@ ott - Saya menganggap kode OP adalah penyederhanaan kasus penggunaannya - misalnya, mereka mungkin memiliki sesuatu yang lebih mirip ret2 = doSomethingElse(ret1), tetapi itu tidak relevan dengan pertanyaan sehingga dihapus.
Izkata
if (dosomething() == false) ...adalah kode yang buruk. Gunakan yang lebih intutiveif (!dosomething()) ...
Steffen Heil
1

Asumsi: Anda sedang mengembangkan dalam kode C #.

Jawaban cepatnya adalah tidak ada performa yang signifikan dari penggunaan blok try / akhirnya. Ada hit kinerja ketika Anda melempar dan menangkap pengecualian.

Jawaban yang lebih panjang adalah melihat sendiri. Jika Anda melihat kode CIL yang mendasarinya dihasilkan ( misalnya reflektor gerbang merah maka Anda dapat melihat instruksi CIL yang mendasarinya dihasilkan dan melihat dampaknya seperti itu.

Michael Shaw
sumber
11
Pertanyaannya ditandai java .
Joachim Sauer
Terlihat dengan baik! ;)
Michael Shaw
3
Juga, metode tidak dikapitalisasi, meskipun itu bisa saja rasanya enak.
Erik Reppen
masih jawaban yang bagus jika pertanyaannya adalah c # :)
PhillyNJ
-2

Seperti yang telah dicatat orang lain, kode ini akan memiliki hasil yang berbeda, jika salah satu dosomethingelseatau dootherstuffmelemparkan pengecualian.

Dan ya, panggilan fungsi apa pun dapat menimbulkan pengecualian, setidaknya a StackOVerflowError! Meskipun jarang mungkin untuk menangani ini dengan benar (karena setiap fungsi pembersihan yang disebut mungkin akan memiliki masalah yang sama, dalam beberapa kasus (seperti menerapkan kunci misalnya) sangat penting untuk bahkan menangani dengan benar ...

Steffen Heil
sumber
2
ini sepertinya tidak menawarkan sesuatu yang substansial daripada 6 jawaban sebelumnya
agas