Hiçbir zaman istisna atılmasa bile try-catch blokları kullanmak pahalı mıdır?


189

İstisnaları yakalamanın pahalı olduğunu biliyoruz. Ancak, bir istisna atılmasa bile Java'da bir try-catch bloğu kullanmak pahalı mıdır?

Stack Overflow soru / cevap buldum Neden denemek blok pahalı? , ancak .NET içindir .


30
Bu sorunun gerçekten bir anlamı yok. Try..catch çok özel bir amaca sahiptir. İhtiyacınız varsa, ihtiyacınız var. Her halükarda, yakalamanın olmadığı bir denemenin anlamı nedir?
JohnFx

49
try { /* do stuff */ } finally { /* make sure to release resources */ }yasal ve kullanışlıdır
A4L

4
Bu maliyetin faydalara karşı tartılması gerekir. Yalnız değil. Her durumda, pahalı görecelidir ve bunu yapamayacağınızı bilinceye kadar, bir şey yapmak yerine en belirgin yöntemi kullanmak mantıklıdır, çünkü bir saat boyunca bir milisaniye veya iki kurtarabilir. program yürütme.
Joel

4
Umarım bu bir "
hadi

6
@SAFX: Java7 ile bile kurtulabilirsiniz finallykullanarak bloğu birtry-with-resources
a_horse_with_no_name

Yanıtlar:


201

tryneredeyse hiçbir masrafı yok. Çalışma tryzamanında kurulum işi yapmak yerine , kodun meta verileri derleme zamanında yapılandırılır, böylece bir istisna atıldığında, şimdi yığını yığmak ve trybunu yakalayacak herhangi bir blok olup olmadığını görmek nispeten pahalı bir işlem yapar istisna. Bir layman'ın bakış açısından, tryözgür de olabilir. Aslında size maliyeti olan istisnayı atıyor - ancak yüzlerce veya binlerce istisna atmadığınız sürece, maliyeti hala fark etmeyeceksiniz.


trybununla ilişkili bazı küçük maliyetleri vardır. Java, tryaksi takdirde yapacağı bir bloktaki kod üzerinde bazı optimizasyonlar yapamaz. Örneğin, Java genellikle daha hızlı çalışmasını sağlamak için bir yöntemdeki talimatları yeniden düzenler - ancak Java ayrıca bir istisna atılırsa, yöntemin yürütülmesinin, kaynak kodda yazıldığı gibi ifadelerine uyulduğunu da garanti etmelidir. bir sıraya kadar.

Çünkü bir tryblokta bir istisna atılabilir (try bloğunun herhangi bir satırında! Bir istisna stop(örneğin, kullanımdan kaldırılmış) çağırma ve hatta OutOfMemoryError hemen hemen her yerde olabilir) gibi bazı istisnalar eşzamansız olarak atılır ve yine de yakalanabilir ve daha sonra aynı yöntemde kod yürütülmeye devam ederse, yapılabilecek optimizasyonlar hakkında akıl yürütmek daha zordur, bu yüzden olma olasılığı daha düşüktür. (Birisi derleyiciyi bunları yapmak, doğrulamak ve doğruluğunu garanti etmek, vb. İçin programlamak zorunda kalacaktır. 'İstisnai' olması gereken bir şey için büyük bir acı olurdu) Ama yine, pratikte böyle şeyleri fark etmeyeceksiniz.


2
Bazı istisnalar zaman uyumsuz olarak atılır, zaman uyumsuz değil güvenli noktalara atılır. ve bu bölüm denemesinin kendisiyle ilişkili bazı küçük maliyetleri vardır. Java, bir deneme bloğunda kodda bazı optimizasyonlar yapamaz , aksi takdirde ciddi bir referansa ihtiyaç duyar. Bir noktada kodun try / catch bloğu içinde olması muhtemeldir. Deneme / yakalama bloğunun eğimli olması ve sonuç için uygun kafes oluşturmanın daha zor olacağı doğru olabilir, ancak yeniden düzenlenmiş kısım belirsizdir.
bestsss

2
Ayrıca try...finallyolmayan bir blok catchbazı optimizasyonları engeller mi?
dajood

5
@Patashu "Aslında size maliyeti olan istisnayı atmak " Teknik olarak, istisnayı atmak pahalı değildir; Exceptionnesneyi başlatmak çoğu zaman gereken şeydir.
Austin D

72

Ölçelim, olur mu?

public abstract class Benchmark {

    final String name;

    public Benchmark(String name) {
        this.name = name;
    }

    abstract int run(int iterations) throws Throwable;

    private BigDecimal time() {
        try {
            int nextI = 1;
            int i;
            long duration;
            do {
                i = nextI;
                long start = System.nanoTime();
                run(i);
                duration = System.nanoTime() - start;
                nextI = (i << 1) | 1;
            } while (duration < 100000000 && nextI > 0);
            return new BigDecimal((duration) * 1000 / i).movePointLeft(3);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public String toString() {
        return name + "\t" + time() + " ns";
    }

    public static void main(String[] args) throws Exception {
        Benchmark[] benchmarks = {
            new Benchmark("try") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        try {
                            x += i;
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                    return x;
                }
            }, new Benchmark("no try") {
                @Override int run(int iterations) throws Throwable {
                    int x = 0;
                    for (int i = 0; i < iterations; i++) {
                        x += i;
                    }
                    return x;
                }
            }
        };
        for (Benchmark bm : benchmarks) {
            System.out.println(bm);
        }
    }
}

Bilgisayarımda bu şöyle bir şey yazdırıyor:

try     0.598 ns
no try  0.601 ns

En azından bu önemsiz örnekte, try ifadesinin performans üzerinde ölçülebilir bir etkisi yoktu. Daha karmaşık olanları ölçmekten çekinmeyin.

Genel olarak konuşursak, kodunuzda gerçek bir performans sorunu olduğunuzu kanıtlayana kadar dil yapılarının performans maliyeti hakkında endişelenmemenizi öneririz. Veya Donald Knuth'un dediği gibi: "erken optimizasyon tüm kötülüklerin köküdür".


4
Deneme / denemenin çoğu JVM'de aynı olması muhtemel olsa da, mikrobenchmark son derece kusurludur.
bestsss

2
oldukça birkaç seviye: Yani sonuçlar 1ns altında hesaplanıyor mu? Derlenmiş kod hem try / catch AND döngüsünü tamamen kaldıracaktır (1'den n'ye kadar olan sayıları toplamak önemsiz bir aritmetik ilerleme toplamıdır). Kod try / nihayetinde derleyici ispatlasa bile, orada atılacak hiçbir şey yoktur. Soyut kodun sadece 2 çağrı sitesi vardır ve klonlanır ve satır içine alınır. Daha fazla vaka var, sadece mikrobenchmark ile ilgili bazı makalelere bakın ve her zaman oluşturulan montajı kontrol eden bir mikrobençmark yazmaya karar verin .
bestsss

3
Bildirilen süreler , döngünün yinelemesi içindir . Bir ölçüm yalnızca toplam süre> 0,1 saniyeden uzunsa (veya burada 2 milyar iterasyon, burada durum böyle değilse) kullanılacağı için, döngünün tamamen kaldırıldığına dair iddianıza inanıyorum - çünkü döngü kaldırıldı, yürütülmesi 0.1 saniye sürdü?
meriton

... ve aslında, buna göre -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly, hem döngü hem de buradaki ekleme üretilen yerel kodda mevcuttur. Ve hayır, soyut yöntemler satır içi değildir, çünkü arayanlar sadece zamanında derlenmemiştir (muhtemelen, yeterince kez çağrılmadığından).
meriton

Java'da doğru bir mikro ölçütü nasıl yazabilirim: stackoverflow.com/questions/504103/…
Vadzim

47

try/ catchPerformans üzerinde bazı etkileri olabilir. Bunun nedeni, JVM'nin bazı optimizasyonlar yapmasını engellemesidir. Joshua Bloch, "Etkili Java" da şunları söyledi:

• Kodu bir try-catch bloğu içine yerleştirmek, modern JVM uygulamalarının başka şekilde gerçekleştirebileceği bazı optimizasyonları engeller.


25
"JVM'nin bazı optimizasyonlar yapmasını engelliyor" ...? Hiç ayrıntıya girebilir misiniz?
Kraken

5
Deneme bloklarının içindeki Kraken Kodu (genellikle? Her zaman?) Örnek olarak deneme bloklarının dışındaki kodla yeniden sipariş edilemez.
Patashu

3
Sorunun, "performans üzerinde herhangi bir etkisi olup olmadığı" değil, "pahalı" olup olmadığına dikkat edin.
mikołak

3
Etkili Java'dan bir alıntı ekledi ve bu elbette java İncil'i; bir referans yoksa alıntı bir şey söylemez. Pratik olarak java herhangi bir kod try / nihayet bir düzeyde.
bestsss

29

Evet, diğerlerinin söylediği gibi, bir tryblok {}onu çevreleyen karakterler arasında bazı optimizasyonları engeller . Özellikle, optimizer, blok içinde herhangi bir noktada bir istisnanın olabileceğini varsaymalıdır, bu nedenle ifadelerin yürütüldüğüne dair bir güvence yoktur.

Örneğin:

    try {
        int x = a + b * c * d;
        other stuff;
    }
    catch (something) {
        ....
    }
    int y = a + b * c * d;
    use y somehow;

Öğesi olmadan, tryatamak için hesaplanan değer xbir "ortak alt ifade" olarak kaydedilebilir ve atamak için yeniden kullanılabilir y. Ancak tryilk ifadenin değerlendirildiğine dair bir güvence olmadığından, ifadenin yeniden hesaplanması gerekir. Bu genellikle "düz çizgi" kodunda çok önemli değildir, ancak bir döngüde önemli olabilir.

Ancak bunun SADECE JITCed kodu için geçerli olduğuna dikkat edilmelidir. Javac sadece bir miktar optimizasyon yapar ve bir tryblok girmek / bırakmak için bayt kodu yorumlayıcısının maliyeti sıfırdır . (Blok sınırlarını işaretlemek için oluşturulan bayt kodları yoktur.)

Ve bestsss için:

public class TryFinally {
    public static void main(String[] argv) throws Throwable {
        try {
            throw new Throwable();
        }
        finally {
            System.out.println("Finally!");
        }
    }
}

Çıktı:

C:\JavaTools>java TryFinally
Finally!
Exception in thread "main" java.lang.Throwable
        at TryFinally.main(TryFinally.java:4)

javap çıkışı:

C:\JavaTools>javap -c TryFinally.class
Compiled from "TryFinally.java"
public class TryFinally {
  public TryFinally();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]) throws java.lang.Throwable;
    Code:
       0: new           #2                  // class java/lang/Throwable
       3: dup
       4: invokespecial #3                  // Method java/lang/Throwable."<init>":()V
       7: athrow
       8: astore_1
       9: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: ldc           #5                  // String Finally!
      14: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      17: aload_1
      18: athrow
    Exception table:
       from    to  target type
           0     9     8   any
}

"GOTO" yok.


Zorunlu olmayan blok sınırlarını işaretlemek için oluşturulan bayt kodları yoktur - GOTO'nun bloktan ayrılmasını gerektirir, aksi takdirde catch/finallyçerçeveye düşer .
bestsss

@bestsss - Bir GOTO oluşturulmuş olsa bile (ki bu verilmez) maliyeti miniktir ve bir blok sınırı için "işaretleyici" olmaktan çok uzaktır - GOTO birçok yapı için üretilebilir.
Hot Licks

Maliyetten hiç bahsetmedim, ancak üretilen hiçbir bayt kodu yanlış bir ifadedir. Bu kadar. Aslında, bayt kodunda blok yoktur, çerçeveler bloklara eşit değildir.
bestsss

Deneme doğrudan nihayet düşerse bir GOTO olmayacak ve GOTO'nun olmayacağı başka senaryolar da olacak. Mesele şu ki, "enter try" / "exit try" bayt kodları sırasında hiçbir şey yoktur.
Hot Licks

Deneme doğrudan nihayetinde düşerse bir GOTO olmayacak - Yanlış! finallybaytkodu yok , bu try/catch(Throwable any){...; throw any;}Ve bir çerçeve ve tanımlanabilir (boş olmayan) ve benzeri OLMALIDIR w / yakalama deyimi var. Neden konu hakkında tartışmaya çalışıyorsunuz, en azından bazı bayt kodlarını kontrol edebilirsiniz? İmpl için geçerli kılavuz. nihayet blokları kopyalamak ve goto bölümünden kaçınmaktır (önceki impl), ancak kaç çıkış noktası olduğuna bağlı olarak bayt kodlarının kopyalanması gerekir.
bestsss

8

Optimizasyonların neden yapılamadığını anlamak için temel mekanizmaları anlamak faydalıdır. Bulabildiğim en kısa örnek C makrolarında şu adreste uygulandı: http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

#include <stdio.h>
#include <setjmp.h>
#define TRY do{ jmp_buf ex_buf__; switch( setjmp(ex_buf__) ){ case 0: while(1){
#define CATCH(x) break; case x:
#define FINALLY break; } default:
#define ETRY } }while(0)
#define THROW(x) longjmp(ex_buf__, x)

Derleyiciler genellikle bir sıçramanın X, Y ve Z'ye yerelleştirilip yerelleştirilemeyeceğini belirlemekte zorluk çekerler, bu nedenle güvenli olmayı garanti edemedikleri optimizasyonları atlarlar, ancak uygulamanın kendisi oldukça hafiftir.


4
Try / catch için bulduğunuz bu C makroları, Java veya C # uygulamasına eşdeğer değildir; bu, 0 çalışma zamanı talimatı yayar.
Patashu

Java uygulaması tam olarak dahil edilemeyecek kadar geniştir, bu, istisnaların nasıl uygulanabileceğine ilişkin temel fikri anlamak amacıyla basitleştirilmiş bir uygulamadır. 0 çalışma zamanı talimatı verdiğini söylemek yanıltıcıdır. Örneğin, basit bir classcastexception, çalıştırılabilirliği genişleten istisnayı genişleten runtimeexception özelliğini genişletir: grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/… ... Bu, C Sadece 1 vaka kullanılıyorsa, hala küçük bir başlangıç ​​yükü vardır.
technosaurus

1
@Patashu Önceden derlenmiş olan bu bitlerin tümü başlangıçta kullanılsa da kullanılmasa da yüklenmelidir. Derleme zamanında çalışma zamanında bellek yetersiz özel durumu olup olmadığını bilmenin bir yolu yoktur - bu yüzden çalışma zamanı istisnaları olarak adlandırılırlar - aksi takdirde derleyici uyarıları / hataları olurlar, bu yüzden hayır her şeyi optimize etmez , bunları işlemek için tüm kodlar derlenmiş koda dahil edilir ve bir başlangıç ​​maliyetine sahiptir.
technosaurus

2
C hakkında konuşamıyorum. C # ve Java'da, kod değil meta veriler ekleyerek deneyin. Bir try bloğu girildiğinde, bunu gösteren hiçbir şey yürütülmez - bir istisna atıldığında, yığın çözülür ve meta veriler bu istisna türünün işleyicileri için kontrol edilir (pahalı).
Patashu

1
Evet, aslında bir Java yorumlayıcısı ve statik bayt kodu derleyicisi uyguladım ve sonraki bir JITC üzerinde (IBM iSeries için) çalıştım ve size bayt kodlarındaki deneme aralığının girişini / çıkışını "işaretleyen" hiçbir şey olmadığını söyleyebilirim, daha doğrusu aralıklar ayrı bir tabloda tanımlanmıştır. Tercüman bir deneme aralığı için özel bir şey yapmaz (istisna oluşana kadar). Bir JITC (veya statik bayt kodu derleyicisi), daha önce belirtildiği gibi optimizasyonları bastırmanın sınırlarının farkında olmalıdır.
Hot Licks

8

Yine başka bir mikrobençmark ( kaynak ).

Bir istisna yüzdesine göre try-catch ve no-try-catch kodu sürümünü ölçtüğüm bir test oluşturdum. % 10, test vakalarının% 10'unun sıfır vakaya bölündüğü anlamına gelir. Bir durumda bir try-catch bloğu, diğerinde bir koşullu operatör tarafından ele alınır. İşte benim sonuçları tablosu:

OS: Windows 8 6.2 x64
JVM: Oracle Corporation Java HotSpot(TM) 64-Bit Server VM 23.25-b01
Yüzde | Sonuç (try / if, ns)   
    % 0 | 88/90   
    % 1 | 89/87    
    % 10 | 86/97    
    % 90 | 85/83   

Bu, bu vakaların hiçbiri arasında önemli bir fark olmadığını söylüyor.


3

NullPointException'ı yakalamak oldukça pahalı buldum. 1.2k operasyonlar için zaman 200ms ve 12ms idi, aynı şekilde ele aldığımda benim için oldukça gelişti if(object==null).

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.