İstisnaların Java'daki performans üzerindeki etkileri nelerdir?


496

Soru: Java'da kural dışı durum işleme gerçekten yavaş mı?

Geleneksel bilgelik ve birçok Google sonucu, olağanüstü mantığın Java'daki normal program akışı için kullanılmaması gerektiğini söylüyor. Genellikle iki sebep verilir,

  1. gerçekten yavaş - normal koddan daha yavaş bir büyüklük sırası bile (verilen nedenler değişir),

ve

  1. dağınıktır çünkü insanlar sadece hataların istisnai kodda ele alınmasını bekler.

Bu soru yaklaşık 1 numara.

Örnek olarak, bu sayfa Java istisna işlemeyi "çok yavaş" olarak tanımlar ve yavaşlığı istisna mesajı dizesinin oluşturulmasıyla ilişkilendirir - "bu dize daha sonra atılan istisna nesnesinin oluşturulmasında kullanılır. Bu hızlı değil." Java'da Etkili Özel Durum İşleme makalesi , "bunun nedeninin, özel durum işlemenin nesne oluşturma yönünden kaynaklandığını ve bu nedenle özel durumları atmayı doğal olarak yavaşlattığını" belirtir. Bunun bir başka nedeni, yığın izleme neslinin onu yavaşlatan şey olmasıdır.

Testlerim (32 bit Linux'ta Java 1.6.0_07, Java HotSpot 10.0 kullanarak), kural dışı durum işlemenin normal koddan daha yavaş olmadığını gösterir. Bazı kod yürüten bir döngüde bir yöntem çalıştırmayı denedim. Yöntemin sonunda, dönmek veya atmak için bir boolean kullanıyorum . Bu şekilde asıl işlem aynıdır. Yöntemleri farklı sıralarda çalıştırmayı ve test sürelerimi ortalamayı denedim, JVM'nin ısındığını düşünmüştüm. Tüm testlerimde, atış daha hızlı olmasa bile en azından geri dönüş kadar hızlıydı (% 3,1'e kadar daha hızlı). Testlerimin yanlış olma olasılığına tamamen açığım, ancak kod örneği, test karşılaştırmaları veya Java'da istisna işlemeyi gösteren son iki yıl içinde sonuçta bir şey görmedim. yavaş.

Beni bu yolda aşağı iten şey, normal kontrol mantığının bir parçası olarak istisnalar atmak için kullanmam gereken bir API idi. Bunları kullanımlarında düzeltmek istedim, ancak şimdi yapamayabilirim. Bunun yerine onların ileri görüşlerini övmek zorunda mıyım?

Tam zamanında derlemede Verimli Java kural dışı durum işleme makalesinde , yazarlar, kural dışı durum işleyicilerin varlığının, yalnızca bir istisna atılmasa bile, JIT derleyicisinin kodu düzgün şekilde optimize etmesini ve böylece yavaşlatmasını sağlamak için yeterli olduğunu önerir. . Bu teoriyi henüz test etmedim.


8
Yaklaşık 2 sormadığınızı biliyorum), ancak program akışı için bir istisna kullanmanın GOTO'ları kullanmaktan daha iyi olmadığını bilmelisiniz. Bazı insanlar goto'ları savunur, bazı insanlar ne hakkında konuştuğunuzu savunurlar, ancak bir süre uygulayan ve sürdüren birine sorarsanız, her ikisinin de tasarım uygulamalarını sürdürmek için fakir olduğunu ve muhtemelen lanetleyeceğini söylerler. bunları kullanma kararını verecek kadar akıllı olduklarını düşünen kişinin adı).
Bill K

80
Program akışı için istisnalar kullanmanın GOTO'ları kullanmaktan daha iyi olmadığını iddia eden Bill, program akışı için koşullu ve döngüler kullanmanın GOTO'ları kullanmaktan daha iyi olmadığını iddia etmekten daha iyi değildir. Kırmızı bir ringa balığı. Kendini tanıt. İstisnalar diğer dillerde program akışı için etkili bir şekilde kullanılabilir ve kullanılır. Deyimsel Python kodu, örneğin düzenli olarak istisnalar kullanır. Bu şekilde (Java değil) istisnalar kullanan kodu koruyabilir ve doğal olarak yanlış bir şey olduğunu sanmıyorum.
mmalone

14
mmalone, normal kontrol akışı için Özel Durumlar'ı kullanarak Java'da kötü bir fikirdir çünkü paradigma seçimi bu şekilde yapılmıştır . Bloch EJ2'yi okuyun - alıntıyı (Madde 57) açıkça belirtiyor - nedenine exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flowdair eksiksiz ve kapsamlı bir açıklama yapıyor. Java lib'i yazan adamdı . Bu nedenle, sınıfların API sözleşmesini tanımlayan kişi odur. / Bill K'yi bu konuda kabul edin.

8
@ OndraŽižka Bazı çerçeve bunu yaparsa (istisnai olmayan durumlarda İstisnalar kullanın), tasarımın kusurlu ve bozuk olması, dilin İstisna sınıfı sözleşmesini bozmasıdır. Bazı insanların berbat kod yazması onu daha az berbat yapmaz.

8
Stackoverflow.com'un yaratıcısı dışında hiçbir şey istisnalar hakkında yanlış değildir. Yazılım geliştirmenin altın kuralı asla basit karmaşık ve hantal bir şey yapmaz. “Basit bir 3 satırlık programın olması gereken şeyin, iyi hata kontrolü yaptığınızda çoğu zaman 48 satıra dönüştüğü doğrudur, ama bu hayattır, ...” Bu, saflık değil, basitlik arayışıdır.
sf_jeff

Yanıtlar:


345

İstisnaların nasıl uygulandığına bağlıdır. En basit yol setjmp ve longjmp kullanmaktır. Bu, CPU'nun tüm kayıtlarının yığına yazıldığı anlamına gelir (zaten biraz zaman alır) ve muhtemelen başka verilerin oluşturulması gerekir ... tüm bunlar zaten try ifadesinde gerçekleşir. Throw ifadesinin yığının çözülmesi ve tüm kayıtların (ve VM'deki diğer olası değerlerin) değerlerini geri yüklemesi gerekir. Bu nedenle, deneme ve atma eşit derecede yavaştır ve bu oldukça yavaştır, ancak bir istisna atılmazsa, try bloğundan çıkmak çoğu durumda zaman almaz (yöntem varsa, her şey yığına otomatik olarak temizlenir).

Sun ve diğerleri, bunun muhtemelen yetersiz olduğunu ve elbette VM'lerin zaman içinde daha hızlı ve daha hızlı olduğunu fark ettiler. İstisnaları uygulamanın başka bir yolu var, bu da kendini hızlı yıldırım yapmayı deniyor (aslında genel olarak denemek için hiçbir şey olmuyor - sınıf VM tarafından yüklendiğinde yapılması gereken her şey zaten yapılır) ve atışı o kadar yavaş yapmaz . Hangi JVM'nin bu yeni, daha iyi tekniği kullandığını bilmiyorum ...

... ancak daha sonra kodunuz belirli bir sistemde yalnızca bir JVM'de çalışacak şekilde Java'da mı yazıyorsunuz? Başka bir platformda veya başka bir JVM sürümünde (muhtemelen başka bir satıcının) çalışabileceği için, hızlı uygulamayı da kullandıklarını kim söyledi? Hızlı olan, yavaş olandan daha karmaşıktır ve tüm sistemlerde kolayca mümkün değildir. Taşınabilir mi kalmak istiyorsun? O zaman hızlı olmanın istisnalarına güvenmeyin.

Ayrıca, bir deneme bloğunda ne yaptığınız konusunda büyük bir fark yaratır. Bir try bloğunu açarsanız ve asla bu try bloğundan herhangi bir yöntem çağırmazsanız, try bloğu ultra hızlı olacaktır, çünkü JIT aslında bir atışı basit bir goto gibi ele alabilir. Bir istisna atılırsa ne yığın durumunu kaydetmesi ne de yığının çözülmesi gerekmez (yalnızca yakalama işleyicilerine atlaması gerekir). Ancak, bu genellikle yaptığınız şey değildir. Genellikle bir try bloğunu açar ve istisna oluşturabilecek bir yöntemi çağırırsınız, değil mi? Ve sadece try bloğunu metodunuzda kullansanız bile, bu ne tür bir metot olacak, bu başka bir metodu çağırmıyor? Sadece bir sayı hesaplayacak mı? O zaman istisnalara ne gerek var? Program akışını düzenlemenin çok daha zarif yolları vardır. Basit matematik dışında hemen hemen her şey için,

Aşağıdaki test koduna bakın:

public class Test {
    int value;


    public int getValue() {
        return value;
    }

    public void reset() {
        value = 0;
    }

    // Calculates without exception
    public void method1(int i) {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            System.out.println("You'll never see this!");
        }
    }

    // Could in theory throw one, but never will
    public void method2(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // Will never be true
        if ((i & 0xFFFFFFF) == 1000000000) {
            throw new Exception();
        }
    }

    // This one will regularly throw one
    public void method3(int i) throws Exception {
        value = ((value + i) / i) << 1;
        // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
        // an AND operation between two integers. The size of the number plays
        // no role. AND on 32 BIT always ANDs all 32 bits
        if ((i & 0x1) == 1) {
            throw new Exception();
        }
    }

    public static void main(String[] args) {
        int i;
        long l;
        Test t = new Test();

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            t.method1(i);
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method1 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method2(i);
            } catch (Exception e) {
                System.out.println("You'll never see this!");
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method2 took " + l + " ms, result was " + t.getValue()
        );

        l = System.currentTimeMillis();
        t.reset();
        for (i = 1; i < 100000000; i++) {
            try {
                t.method3(i);
            } catch (Exception e) {
                // Do nothing here, as we will get here
            }
        }
        l = System.currentTimeMillis() - l;
        System.out.println(
            "method3 took " + l + " ms, result was " + t.getValue()
        );
    }
}

Sonuç:

method1 took 972 ms, result was 2
method2 took 1003 ms, result was 2
method3 took 66716 ms, result was 2

Deneme bloğundaki yavaşlama, arka plan işlemleri gibi karıştırıcı faktörleri ekarte etmek için çok küçük. Ancak av bloğu her şeyi öldürdü ve 66 kat yavaşlattı!

Dediğim gibi, aynı yöntemi (method3) içinde denemek / yakalamak ve atmak eğer sonuç o kadar da kötü olmayacak, ama bu güvenmeyeceğim özel bir JIT optimizasyonu. Ve bu optimizasyonu kullanırken bile, atış hala oldukça yavaş. Bu yüzden burada ne yapmaya çalıştığınızı bilmiyorum, ama bunu yapmanın try / catch / throw kullanmaktan daha iyi bir yolu var.


7
Harika bir cevap ama bunu bildiğim kadarıyla eklemek istiyorum ki System.currentTimeMillis () değil, performansı ölçmek için System.nanoTime () kullanılmalıdır.
Simon Forsberg

10
@ SimonAndréForsberg nanoTime()Java 1.5 gerektirir ve yukarıdaki kodu yazmak için kullandığım sistemde yalnızca Java 1.4 vardı. Ayrıca pratikte çok büyük bir rol oynamaz. İkisi arasındaki tek fark, birinin nanosaniye diğeri milisaniye olması ve nanoTimesaat manipülasyonlarından etkilenmemesidir (siz veya sistem süreci, sistem saatini test kodunun çalıştığı anda tam olarak değiştirmedikçe, önemsizdir). Genellikle haklısın, nanoTimeelbette, daha iyi bir seçim.
Mecki

2
Testinizin aşırı bir durum olduğu unutulmamalıdır. Bir trybloğa sahip kod için çok küçük bir performans isabeti gösterirsiniz , ancak hayır throw. Kişisel throwTest istisnaları atıyor zamanın% 50 o geçer try. Bu açıkça başarısızlığın istisnai olmadığı bir durumdur . Bunu sadece% 10'a kadar düşürmek, performans vuruşunu büyük ölçüde azaltır. Bu tür testlerle ilgili sorun, insanları istisnaları tamamen kullanmayı bırakmaya teşvik etmesidir. İstisnai durumları kullanmak, istisnai durum yönetimi için testinizin gösterdiklerinden çok daha iyi performans gösterir.
Nate

1
@Nate Öncelikle, tüm bunların istisnaların nasıl uygulandığına bağlı olduğunu çok açık bir şekilde söyledim. Sadece BİR spesifik uygulamayı test ediyordum, ancak çok şey var ve Oracle her sürümde tamamen farklı bir uygulama seçebilir. İkincisi, istisnalar sadece istisnai ise, genellikle ne oldukları, elbette etkisi daha küçüktür, bu o kadar açıktır ki, birisinin bunu açıkça işaret etmesi gerektiğini düşünmüyorum ve bu yüzden burada amacınızı hiç anlayamıyorum. Üçüncüsü, istisna, aşırı kullanımı istisna, herkes bunu kabul ediyor, bu yüzden onları çok dikkatli kullanmak çok iyi bir şey.
Mecki

4
@Glide Atış, temizliğe benzemez return. Vücudun ortasında bir yerde, hatta bir operasyonun ortasında bile (şimdiye kadar sadece% 50 oranında tamamlanmış) bir yöntem bırakır ve catchblok yukarı doğru 20 yığın karesi olabilir (bir yöntem, bir tryblok, çağrı yöntemi1'i çağırır, bu yöntem mehtod3, ... öğesini çağıran ve işlemin ortasında method20'de bir istisna atılır). Yığın 20 kareyi yukarı doğru gevşetmeli, tüm bitmemiş işlemler geri alınmalıdır (işlemler yarı yarıya yapılmamalıdır) ve CPU kayıtları temiz durumda olmalıdır. Bunların hepsi zaman harcıyor.
Mecki

255

Bilginize, Mecki'nin yaptığı deneyi uzattım:

method1 took 1733 ms, result was 2
method2 took 1248 ms, result was 2
method3 took 83997 ms, result was 2
method4 took 1692 ms, result was 2
method5 took 60946 ms, result was 2
method6 took 25746 ms, result was 2

İlk 3'ü Mecki'ninkiyle aynı (dizüstü bilgisayarım açıkça daha yavaş).

method4, method3 ile aynıdır, ancak new Integer(1)yapmak yerine bir tane oluşturur throw new Exception().

method5, method3 gibidir, ancak onu new Exception()atmadan oluşturur .

method6, method3 gibidir, ancak yeni bir tane oluşturmak yerine önceden oluşturulmuş bir istisna (örnek değişkeni) atar.

Java'da, bir istisna atma masrafının çoğu, istisna nesnesi oluşturulduğunda oluşan yığın izlemesini toplamak için harcanan zamandır. İstisnayı atmanın gerçek maliyeti, büyük olsa da, istisnayı yaratmanın maliyetinden oldukça düşüktür.


48
+1 Cevabınız temel sorunu ele alıyor - yığını çözmek ve izlemek için harcanan zaman ve ikincil olarak hatanın atılması. Bunu son cevap olarak seçerdim.
Mühendis

8
Güzel. ~% 70 istisna yaratıyor, ~% 30 atıyor. iyi bilgi.
chaqke

1
@Basil - Yukarıdaki rakamlardan anlayabilmelisiniz.
Hot Licks

1
Bu uygulamaya özgü olabilir. Bu karşılaştırmalar için hangi Java sürümü kullanıldı?
Thorbjørn Ravn Andersen

3
Standart kodda, istisnalar oluşturma ve atma işleminin nadir durumlarda (yani çalışma zamanında) gerçekleştiğini, durum böyle değilse, çalışma zamanının koşullarının çok kötü olduğunu veya tasarımın kendisinin sorun olduğunu belirtebiliriz; her iki durumda da performanslar endişe verici değildir ...
Jean-Baptiste Yunès

70

Aleksey Shipilëv, Java istisnalarını çeşitli koşullar kombinasyonlarında karşılaştırdığı çok kapsamlı bir analiz yaptı :

  • Yeni oluşturulan istisnalar ve önceden oluşturulmuş istisnalar
  • Yığın izleme etkin - devre dışı
  • İstenen yığın izlemesi vs asla talep edilmedi
  • Her seviyede zincirlenmiş / sarılmış vs her seviyede yeniden yakalanmış en üst düzeyde yakalandı
  • Çeşitli Java çağrı yığını derinliği
  • Satır içi optimizasyona karşı aşırı satır içi ayarlara karşı varsayılan ayar yok
  • Kullanıcı tanımlı alanlar okunuyor veya okunmuyor

Ayrıca, bunları çeşitli hata frekans seviyelerinde bir hata kodunu kontrol etme performansıyla karşılaştırır.

Sonuçlar (görevinden kelimesi kelimesine alıntılanmıştır):

  1. Gerçekten istisnai istisnalar güzel performans gösterir. Onları tasarlandıkları gibi kullanırsanız ve gerçek kural dışı durumları yalnızca normal kodla ele alınan çok fazla sayıda istisnai olmayan durum arasında iletirseniz, istisnalar kullanmak performans kazancıdır.

  2. İstisnaların performans maliyetlerinin iki ana bileşeni vardır: İstisna başlatıldığında yığın iz yapısı ve İstisna atımı sırasında yığın gevşemesi .

  3. Yığın izleme inşaat maliyetleri, istisna somutlaştırma anındaki yığın derinliği ile orantılıdır . Bu zaten kötü çünkü Dünya'da kim bu fırlatma yönteminin çağrılacağı yığın derinliğini biliyor? Yığın izleme oluşturmayı kapatır ve / veya istisnaları önbelleğe alsanız bile, performans maliyetinin yalnızca bu bölümünden kurtulabilirsiniz.

  4. Yığın çözme maliyetleri, istisna işleyiciyi derlenmiş kodda daha da yaklaştırdığımızda ne kadar şanslı olduğumuza bağlıdır. Dikkatle derin istisna işleyicileri arama önlemek için kodu yapılandırmak muhtemelen daha şanslı olsun yardımcı oluyor.

  5. Her iki etkiyi de ortadan kaldırırsak, istisnaların performans maliyeti yerel şubenin maliyetidir. Ne kadar güzel olursa olsun, bu, olağan kontrol akışı olarak İstisnalar kullanmanız gerektiği anlamına gelmez, çünkü bu durumda derleyiciyi optimize etmenin merhametindesiniz! Bunları yalnızca istisna sıklığının gerçek istisnayı yükseltmenin olası şanssız maliyetini amorti ettiği gerçekten istisnai durumlarda kullanmalısınız .

  6. İyimser başparmak kuralı istisnalar için 10 ^ -4 sıklığı gibi görünüyor yeterince istisnai. Bu, elbette, istisnaların kendilerinin ağır ağırlıklarına, istisna işleyicilerinde yapılan kesin işlemlere vb. Bağlıdır.

Sonuç olarak, bir istisna atılmadığında bir maliyet ödemezsiniz, bu nedenle istisnai durum yeterince nadir olduğunda istisna işleme ifher seferinde kullanmaktan daha hızlıdır . Yazının tamamı okunmaya değer.


41

Cevabım maalesef burada yayınlamak için çok uzun. Burada özetleyeyim ve cesur ayrıntılar için sizi http://www.fuwjax.com/how-slow-are-java-exceptions/ adresine yönlendireyim .

Buradaki asıl soru "asla başarısız olmayan kod" ile karşılaştırıldığında "başarısızlıklar istisna olarak bildirildi" ne kadar yavaş değil? " kabul edilen cevap size inandırabilir. Bunun yerine, soru "diğer yollarla bildirilen hatalarla karşılaştırıldığında" hatalar istisna olarak rapor edilir "ne kadar yavaş olmalıdır?" Genel olarak, arızaları bildirmenin diğer iki yolu ya sentinel değerleriyle ya da sonuç sarmalayıcılarıdır.

Sentinel değerleri, başarı durumunda bir sınıfı, başarısızlık durumunda bir sınıfı döndürme girişimidir. Bunu neredeyse bir istisna atmak yerine bir istisna döndürmek olarak düşünebilirsiniz. Bu, başarı nesnesiyle paylaşılan bir üst sınıf gerektirir ve daha sonra "instanceof" denetimi ve başarı veya başarısızlık bilgisini almak için birkaç yayın gerektirir.

Tip güvenliği riski altında, Sentinel değerlerinin istisnalardan daha hızlı olduğu, ancak sadece kabaca 2x faktörüyle ortaya çıktığı görülüyor. Şimdi, bu çok gibi görünebilir, ancak 2x sadece uygulama farkının maliyetini kapsar. Uygulamada, başarısız olabilecek yöntemlerimiz bu sayfanın başka bir yerindeki örnek kodda olduğu gibi birkaç aritmetik operatörden çok daha ilginç olduğu için faktör çok daha düşüktür.

Sonuç Öte yandan Sarmalayıcılar tip güvenliğinden asla ödün vermiyorlar. Başarı ve başarısızlık bilgilerini tek bir sınıfta toplarlar. Yani "instanceof" yerine "isSuccess ()" ve hem başarı hem de başarısızlık nesneleri için getters sağlarlar. Ancak, sonuç nesneleri özel durumlar kullanmaktan kabaca 2 kat daha yavaştır . Her seferinde yeni bir sarmalayıcı nesnesi oluşturmanın bazen bir istisna atmaktan çok daha pahalı olduğu ortaya çıkıyor.

Bunun da ötesinde, istisnalar, bir yöntemin başarısız olabileceğini belirtmek için sağlanan dildir. Yalnızca API'dan hangi yöntemlerin her zaman (çoğunlukla) çalışması ve hangilerinin başarısızlık bildirmesi gerektiğini söylemenin başka bir yolu yoktur.

İstisnalar sentinellerden daha güvenli, sonuç nesnelerine göre daha hızlı ve ikisinden de daha az şaşırtıcıdır. Ben / else yerine deneyin / yakalamanızı önermiyorum, ancak istisnalar iş mantığında bile başarısızlığı bildirmenin doğru yoludur.

Bununla birlikte, karşılaştığım performansı önemli ölçüde etkilemenin en sık iki yolunun gereksiz nesneler ve iç içe döngüler oluşturduğunu belirtmek isterim. Kural dışı durum oluşturma veya kural dışı durum oluşturma arasında bir seçeneğiniz varsa, kural dışı durumu oluşturmayın. Bazen istisna oluşturma veya her zaman başka bir nesne oluşturma arasında bir seçiminiz varsa, istisnayı oluşturun.


5
Raporlamadan hata olup olmadığını kontrol eden bir kontrol uygulamasına kıyasla üç uygulamanın uzun vadeli performansını test etmeye karar verdim. İşlemin başarısızlık oranı yaklaşık% 4'tür. Bir testin tekrarlanması, stratejilerden birine karşı süreci 10000 kez başlatır. Her strateji 1000 kez test edilir ve istatistikleri oluşturmak için son 900 kez kullanılır. İşte
nanos

2
Sadece eğlence için istisna testinde fillInStackTrace'i devre dışı bıraktım.
Şuanki

Fuwjax, bir şey eksik olmadıkça (ve blog yazınızı değil, sadece SO yazınızı okuduğumu itiraf ediyorum), yukarıdaki iki yorumunuz yayınınızla çelişiyor gibi görünüyor. Karşılaştırmanızda daha düşük rakamların daha iyi olduğunu düşünüyorum, değil mi? Bu durumda, fillInStackTrace etkinleştirilmiş (varsayılan ve olağan davranış) istisnalar oluşturmak, açıkladığınız diğer iki tekniğe göre daha yavaş performans sağlar. Bir şey mi kaçırıyorum yoksa yayınınızı onaylamak için yorum yaptınız mı?
Felix GV

@Fuwjax - burada sunduğunuz "kaya ve sert yer" seçiminden kaçınmanın yolu, "başarıyı" temsil eden bir nesneyi önceden ayırmaktır . Genellikle ortak hata durumları için nesneler önceden tahsis edilebilir. Daha sonra sadece nadiren ek ayrıntıların geri gönderilmesi durumunda, yeni bir nesne yaratılır. (Bu, tamsayı "hata kodları" nın OO eşdeğeri, ayrıca son hatanın ayrıntılarını almak için ayrı bir çağrıdır - onlarca yıldır var olan bir tekniktir.)
ToolmakerSteve

@Fuwjax Yani istisna atmak hesabınız tarafından bir nesne oluşturmuyor mu? Bu muhakemeyi anladığımdan emin değilim. İstisna atarsanız veya sonuç nesnesini döndürürseniz, nesneler yaratırsınız. Bu anlamda sonuç nesneleri bir istisna atmaktan daha yavaş değildir.
Matthias

20

@Mecki ve @incarnate tarafından verilen yanıtları Java için stacktrace dolgusu olmadan genişletiyorum .

Java 7+ ile kullanabiliriz Throwable(String message, Throwable cause, boolean enableSuppression,boolean writableStackTrace). Ancak Java6 için bu soruya verdiğim cevaba bakın

// This one will regularly throw one
public void method4(int i) throws NoStackTraceThrowable {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceThrowable();
    }
}

// This one will regularly throw one
public void method5(int i) throws NoStackTraceRuntimeException {
    value = ((value + i) / i) << 1;
    // i & 1 is equally fast to calculate as i & 0xFFFFFFF; it is both
    // an AND operation between two integers. The size of the number plays
    // no role. AND on 32 BIT always ANDs all 32 bits
    if ((i & 0x1) == 1) {
        throw new NoStackTraceRuntimeException();
    }
}

public static void main(String[] args) {
    int i;
    long l;
    Test t = new Test();

    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method4(i);
        } catch (NoStackTraceThrowable e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method4 took " + l + " ms, result was " + t.getValue() );


    l = System.currentTimeMillis();
    t.reset();
    for (i = 1; i < 100000000; i++) {
        try {
            t.method5(i);
        } catch (RuntimeException e) {
            // Do nothing here, as we will get here
        }
    }
    l = System.currentTimeMillis() - l;
    System.out.println( "method5 took " + l + " ms, result was " + t.getValue() );
}

Java 1.6.0_45 ile çıktı, Core i7, 8GB RAM'de:

method1 took 883 ms, result was 2
method2 took 882 ms, result was 2
method3 took 32270 ms, result was 2 // throws Exception
method4 took 8114 ms, result was 2 // throws NoStackTraceThrowable
method5 took 8086 ms, result was 2 // throws NoStackTraceRuntimeException

Bu nedenle, istisnalar atan yöntemlere kıyasla değerleri döndüren yöntemler daha hızlıdır. IMHO, hem başarı hem de hata akışları için yalnızca dönüş türlerini kullanarak net bir API tasarlayamıyoruz. Yığın izlemesi olmadan istisnalar atan yöntemler, normal İstisnalardan 4-5 kat daha hızlıdır.

Düzenle: NoStackTraceThrowable.java Teşekkürler @Greg

public class NoStackTraceThrowable extends Throwable { 
    public NoStackTraceThrowable() { 
        super("my special throwable", null, false, false);
    }
}

ilginç, teşekkürler. İşte eksik sınıf bildirimi:public class NoStackTraceThrowable extends Throwable { public NoStackTraceThrowable() { super("my special throwable", null, false, false); } }
Greg

Yazdınız With Java 7+, we can useama daha sonra yazdınız , Output with Java 1.6.0_45,bu yüzden bu Java 6 veya 7 sonucu mu?
WBAR

1
Java 7'den @WBAR, sadece arg Throwableolan yapıcıyı kullanmamız gerekiyor boolean writableStackTrace. Ancak bu Java 6 ve önceki sürümlerde mevcut değildir. Bu yüzden Java 6 ve altı için özel uygulama verdim. Yukarıdaki kod Java 6 ve altı için geçerlidir. Lütfen 2. paragrafın 1. satırını dikkatlice okuyunuz.
manikanta

@manikanta "IMHO, hem başarı hem de hata akışları için yalnızca dönüş türlerini kullanarak açık bir API tasarlayamıyoruz."
Hejazzman

@Hejazzman katılıyorum. Ama Optionalbenzerleri Java'ya biraz geç geldi. Bundan önce, başarı / hata bayraklarına sahip sarıcı nesneler kullandık. Ama bu biraz hack gibi görünüyor ve bana doğal gelmiyor.
manikanta

8

Bir süre önce, iki yaklaşımı kullanarak dizeleri ints'e dönüştürmenin göreceli performansını test etmek için bir sınıf yazdım: (1) Integer.parseInt () öğesini çağırın ve istisnayı yakalayın veya (2) dizeyi bir regex ve call parseInt () ile eşleştirin sadece maç başarılı olursa. Normal ifadeyi olabildiğince verimli bir şekilde kullandım (yani döngüyü eklemeden önce Pattern ve Matcher nesnelerini oluşturma) ve yığınları istisnalardan yazdırmadım veya kaydetmedim.

On bin dizenin bir listesi için, bunların tümü geçerli sayılarsa, parseInt () yaklaşımı normal ifade yaklaşımının dört katıdır. Ancak, dizelerin yalnızca% 80'i geçerliyse, normal ifade parseInt () yönteminden iki kat daha hızlıydı. Ve% 20 geçerliyse, istisna atıldı ve zamanın% 80'ini yakaladı, normal ifade parseInt () kadar yirmi kat daha hızlıydı.

Normal ifade yaklaşımının geçerli dizeleri iki kez işlediğini düşünerek sonuç beni şaşırttı: bir kez maç için ve tekrar parseInt () için. Ancak bunun için yapılan istisnalardan daha fazla istisna fırlatmak ve yakalamak. Bu tür bir durumun gerçek dünyada çok sık ortaya çıkması muhtemel değildir, ancak eğer öyleyse, kesinlikle istisna yakalama tekniğini kullanmamalısınız. Ancak yalnızca kullanıcı girişini veya bunun gibi bir şeyi doğrulıyorsanız, elbette parseInt () yaklaşımını kullanın.


hangi JVM'yi kullandın? sun-jdk 6 ile hala bu kadar yavaş mı?
Benedikt Waldvogel

Bu cevabı göndermeden önce kazdım ve tekrar JDK 1.6u10 altında çalıştırdım ve bunlar gönderdiğim sonuçlar.
Alan Moore

Bu çok, çok kullanışlı! Teşekkürler. Her zamanki kullanım durumlarım için kullanıcı girişlerini ayrıştırmam gerekiyor (benzer bir şey kullanarak Integer.ParseInt()) ve çoğu zaman kullanıcı girdisinin doğru olmasını bekliyorum, bu yüzden kullanım durumum için ara sıra istisna atmak gibi görünüyor .
markvgti

8

İlk makale, çağrı yığınını çaprazlama ve pahalı kısım olarak bir yığın izlemesi oluşturma eylemine atıfta bulunuyor ve ikinci makale bunu söylemese de, bu, nesne oluşturmanın en pahalı kısmı olduğunu düşünüyorum. John Rose'un istisnaları hızlandırmak için farklı teknikler tanımladığı bir makalesi var . (İstisnaları, yığın izleri olmayan istisnaları vb. Önceden konumlandırma ve yeniden kullanma)

Ama yine de - bence bu sadece gerekli bir kötülük, son çare olarak düşünülmeli. John'un bunu yapmasının nedeni, JVM'de henüz mevcut olmayan (henüz) diğer dillerdeki özellikleri taklit etmektir. Kontrol akışı için istisnalar kullanma alışkanlığı edinmemelisiniz. Özellikle performans nedeniyle değil! # 2'de kendinizden bahsettiğiniz gibi, kodunuzdaki ciddi hataları maskeleme riskiniz vardır ve yeni programcıların bakımı daha zor olacaktır.

Java'daki mikrobenç işaretler, özellikle JIT bölgesine girdiğinizde, doğru bir şekilde elde edilmesi şaşırtıcı bir şekilde zor (söylendi), bu yüzden gerçekten istisnalar kullanmanın gerçek hayatta "dönüş" ten daha hızlı olduğundan şüphe ediyorum. Örneğin, testinizde 2 ila 5 yığın kareniz olduğundan şüpheleniyor musunuz? Şimdi kodunuzun JBoss tarafından dağıtılan bir JSF bileşeni tarafından çağrıldığını düşünün. Şimdi birkaç sayfa uzunluğunda bir yığın iziniz olabilir.

Belki test kodunuzu gönderebilirsiniz?


7

Bu konuların ilgili olup olmadığını bilmiyorum, ama bir zamanlar mevcut iş parçacığının yığın izlemesine dayanan bir numara uygulamak istedim: Anlatılan sınıfın içindeki somutlaştırmayı tetikleyen yöntemin adını keşfetmek istedim (yeap, fikir çılgınca, Tamamen vazgeçtim). O çağrı keşfetti ben Yani Thread.currentThread().getStackTrace()olduğunu son derece (nedeniyle yerli yavaş dumpThreadsdahili olarak kullandığı yöntemle).

Dolayısıyla Java'nın Throwableyerel bir yöntemi vardır fillInStackTrace. Daha catchönce açıklanan katil bloğun bir şekilde bu yöntemin yürütülmesini tetiklediğini düşünüyorum .

Ama sana başka bir hikaye anlatayım ...

Scala'da bazı işlevsel özellikler JVM'de derlenir ControlThrowable, bu da Throwableonu fillInStackTraceaşağıdaki şekilde genişletir ve geçersiz kılar :

override def fillInStackTrace(): Throwable = this

Bu yüzden yukarıdaki testi uyarladım (döngü miktarı on azalır, makinem biraz daha yavaştır :):

class ControlException extends ControlThrowable

class T {
  var value = 0

  def reset = {
    value = 0
  }

  def method1(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      println("You'll never see this!")
    }
  }

  def method2(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0xfffffff) == 1000000000) {
      throw new Exception()
    }
  }

  def method3(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new Exception()
    }
  }

  def method4(i: Int) = {
    value = ((value + i) / i) << 1
    if ((i & 0x1) == 1) {
      throw new ControlException()
    }
  }
}

class Main {
  var l = System.currentTimeMillis
  val t = new T
  for (i <- 1 to 10000000)
    t.method1(i)
  l = System.currentTimeMillis - l
  println("method1 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method2(i)
  } catch {
    case _ => println("You'll never see this")
  }
  l = System.currentTimeMillis - l
  println("method2 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method4(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method4 took " + l + " ms, result was " + t.value)

  t.reset
  l = System.currentTimeMillis
  for (i <- 1 to 10000000) try {
    t.method3(i)
  } catch {
    case _ => // do nothing
  }
  l = System.currentTimeMillis - l
  println("method3 took " + l + " ms, result was " + t.value)

}

Yani sonuçlar:

method1 took 146 ms, result was 2
method2 took 159 ms, result was 2
method4 took 1551 ms, result was 2
method3 took 42492 ms, result was 2

Sen arasındaki tek fark, bkz method3ve method4onlar Farklı istisnaları atmak olmasıdır. Yeap, method4hala method1ve daha yavaş method2, ama fark çok daha kabul edilebilir.


6

JVM 1.5 ile bazı performans testleri yaptım ve istisnaları kullanmak en az 2 kat daha yavaştı. Ortalama olarak: İstisnalar hariç, üç katından (3x) fazla önemsiz derecede küçük bir yöntemle uygulama süresi İstisnayı yakalamak zorunda olan önemsiz derecede küçük bir döngü, zamanında 2 kat artış gördü.

Mikro kodlamaların yanı sıra üretim kodunda da benzer rakamlar gördüm.

İstisnalar kesinlikle sıkça adlandırılan hiçbir şey için KULLANILMAMALIDIR . Saniyede binlerce istisna atmak büyük bir şişe boynuna neden olur.

Örneğin, çok büyük bir metin dosyasındaki tüm kötü değerleri bulmak için "Integer.ParseInt (...)" kullanılması çok kötü bir fikirdir. (Bu yarar yönteminin üretim kodundaki performansı öldürdüğünü gördüm )

Bir kullanıcı GUI formunda kötü bir değeri bildirmek için bir istisna kullanmak, muhtemelen performans açısından o kadar da kötü değildir.

İyi bir tasarım uygulaması olsun ya da olmasın, kurala giderdim: hata normal / beklenen ise, bir dönüş değeri kullanın. Anormalse, bir istisna kullanın. Örneğin: kullanıcı girişlerini okumak, hatalı değerler normal - bir hata kodu kullanın. Dahili yardımcı program işlevine bir değer iletilirken, hatalı değerler kod çağrılarak filtrelenmelidir - bir istisna kullanın.


Yapabileceğim bazı şeyler önereyim: Integer.valueOf (String) kullanmak yerine formda bir sayıya ihtiyacınız varsa, bunun yerine normal ifade eşleştirici kullanmayı düşünmelisiniz. Kalıbı önceden derleyebilir ve yeniden kullanabilirsiniz, böylece eşleştiriciler yapmak ucuzdur. Bununla birlikte, bir GUI formunda, isValid / validate / checkField veya sahip olduğunuz şeye sahip olmak muhtemelen daha açıktır. Ayrıca, Java 8 ile İsteğe bağlı monad'larımız var, bu yüzden bunları kullanmayı düşünün. (cevap 9 yaşında, ama yine de
!:

4

Java ve C # 'da istisna performansı istenecek çok şey bırakıyor.

Programcılar olarak bu, bizi pratik performans nedenlerinden ötürü, "istisnalara nadiren neden olunmalıdır" kuralıyla yaşamaya zorluyor.

Ancak, bilgisayar bilimcileri olarak, bu sorunlu duruma karşı isyan etmeliyiz. Bir işlevi yazan kişinin genellikle ne sıklıkta çağrılacağı veya başarı veya başarısızlığın daha muhtemel olup olmadığı hakkında hiçbir fikri yoktur. Yalnızca arayan kişi bu bilgiye sahiptir. İstisnalardan kaçınmaya çalışmak, bazı durumlarda yalnızca temiz ama yavaş istisna sürümlerine sahip olduğumuz belirsiz bazı API idomlarına yol açar ve diğer durumlarda hızlı ancak tıkalı dönüş değeri hatalarımız vardır ve yine de diğer durumlarda her ikisiyle de sonuçlanırız . Kütüphane uygulayıcısının iki API sürümü yazması ve bakımını yapması gerekebilir ve arayan her durumda iki sürümden hangisinin kullanılacağına karar vermelidir.

Bu biraz karışıklık. İstisnalar daha iyi performans gösterdiyse, bu aksak deyimleri önleyebilir ve istisnaları kullanılması gerektiği gibi kullanabiliriz ... yapılandırılmış bir hata geri dönüş tesisi olarak.

Dönüş değerlerine daha yakın teknikler kullanılarak uygulanan istisna mekanizmalarını gerçekten görmek istiyorum, bu yüzden değerleri döndürmek için daha yakın performansa sahip olabiliriz.

İstisna performansını hata-dönüş değeri performansıyla karşılaştıran bir kod örneği.

herkese açık sınıf TestIt {

int value;


public int getValue() {
    return value;
}

public void reset() {
    value = 0;
}

public boolean baseline_null(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        return shouldfail;
    } else {
        return baseline_null(shouldfail,recurse_depth-1);
    }
}

public boolean retval_error(boolean shouldfail, int recurse_depth) {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            return false;
        } else {
            return true;
        }
    } else {
        boolean nested_error = retval_error(shouldfail,recurse_depth-1);
        if (nested_error) {
            return true;
        } else {
            return false;
        }
    }
}

public void exception_error(boolean shouldfail, int recurse_depth) throws Exception {
    if (recurse_depth <= 0) {
        if (shouldfail) {
            throw new Exception();
        }
    } else {
        exception_error(shouldfail,recurse_depth-1);
    }

}

public static void main(String[] args) {
    int i;
    long l;
    TestIt t = new TestIt();
    int failures;

    int ITERATION_COUNT = 100000000;


    // (0) baseline null workload
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                t.baseline_null(shoulderror,recurse_depth);
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("baseline: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }


    // (1) retval_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                if (!t.retval_error(shoulderror,recurse_depth)) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("retval_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);
        }
    }

    // (2) exception_error
    for (int recurse_depth = 2; recurse_depth <= 10; recurse_depth+=3) {
        for (float exception_freq = 0.0f; exception_freq <= 1.0f; exception_freq += 0.25f) {            
            int EXCEPTION_MOD = (exception_freq == 0.0f) ? ITERATION_COUNT+1 : (int)(1.0f / exception_freq);            

            failures = 0;
            long start_time = System.currentTimeMillis();
            t.reset();              
            for (i = 1; i < ITERATION_COUNT; i++) {
                boolean shoulderror = (i % EXCEPTION_MOD) == 0;
                try {
                    t.exception_error(shoulderror,recurse_depth);
                } catch (Exception e) {
                    failures++;
                }
            }
            long elapsed_time = System.currentTimeMillis() - start_time;
            System.out.format("exception_error: recurse_depth %s, exception_freqeuncy %s (%s), time elapsed %s ms\n",
                    recurse_depth, exception_freq, failures,elapsed_time);              
        }
    }
}

}

İşte sonuçlar:

baseline: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 683 ms
baseline: recurse_depth 2, exception_freqeuncy 0.25 (0), time elapsed 790 ms
baseline: recurse_depth 2, exception_freqeuncy 0.5 (0), time elapsed 768 ms
baseline: recurse_depth 2, exception_freqeuncy 0.75 (0), time elapsed 749 ms
baseline: recurse_depth 2, exception_freqeuncy 1.0 (0), time elapsed 731 ms
baseline: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 923 ms
baseline: recurse_depth 5, exception_freqeuncy 0.25 (0), time elapsed 971 ms
baseline: recurse_depth 5, exception_freqeuncy 0.5 (0), time elapsed 982 ms
baseline: recurse_depth 5, exception_freqeuncy 0.75 (0), time elapsed 947 ms
baseline: recurse_depth 5, exception_freqeuncy 1.0 (0), time elapsed 937 ms
baseline: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1154 ms
baseline: recurse_depth 8, exception_freqeuncy 0.25 (0), time elapsed 1149 ms
baseline: recurse_depth 8, exception_freqeuncy 0.5 (0), time elapsed 1133 ms
baseline: recurse_depth 8, exception_freqeuncy 0.75 (0), time elapsed 1117 ms
baseline: recurse_depth 8, exception_freqeuncy 1.0 (0), time elapsed 1116 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 742 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 743 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 734 ms
retval_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 723 ms
retval_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 728 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 920 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 1121   ms
retval_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 1037 ms
retval_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 1141   ms
retval_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 1130 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1218 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 1334  ms
retval_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 1478 ms
retval_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 1637 ms
retval_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 1655 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.0 (0), time elapsed 726 ms
exception_error: recurse_depth 2, exception_freqeuncy 0.25 (24999999), time elapsed 17487   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.5 (49999999), time elapsed 33763   ms
exception_error: recurse_depth 2, exception_freqeuncy 0.75 (99999999), time elapsed 67367   ms
exception_error: recurse_depth 2, exception_freqeuncy 1.0 (99999999), time elapsed 66990 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.0 (0), time elapsed 924 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.25 (24999999), time elapsed 23775  ms
exception_error: recurse_depth 5, exception_freqeuncy 0.5 (49999999), time elapsed 46326 ms
exception_error: recurse_depth 5, exception_freqeuncy 0.75 (99999999), time elapsed 91707 ms
exception_error: recurse_depth 5, exception_freqeuncy 1.0 (99999999), time elapsed 91580 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.0 (0), time elapsed 1144 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.25 (24999999), time elapsed 30440 ms
exception_error: recurse_depth 8, exception_freqeuncy 0.5 (49999999), time elapsed 59116   ms
exception_error: recurse_depth 8, exception_freqeuncy 0.75 (99999999), time elapsed 116678 ms
exception_error: recurse_depth 8, exception_freqeuncy 1.0 (99999999), time elapsed 116477 ms

Dönüş değerlerini kontrol etmek ve yaymak, taban-sıfır çağrısına karşı biraz maliyet ekler ve bu maliyet, çağrı derinliği ile orantılıdır. 8 numaralı bir çağrı zinciri derinliğinde, hata-dönüş-değer kontrol versiyonu, dönüş değerlerini kontrol etmeyen temel versiyondan yaklaşık% 27 daha yavaştı.

Buna karşılık istisna performansı, çağrı derinliğinin değil istisna frekansının bir fonksiyonudur. Bununla birlikte, istisna frekansı arttıkça degredasyon çok daha dramatiktir. Sadece% 25 hata frekansında kod 24 TIMES daha yavaş çalışıyordu. % 100 hata frekansında, istisna sürümü neredeyse 100 TIMES daha yavaştır.

Bu bana, istisna uygulamalarımızda belki de yanlış ödünler verdiğini gösteriyor. İstisnalar, pahalı sap yürüyüşlerinden kaçınarak ya da derleyici tarafından desteklenen dönüş değeri kontrolüne dönüştürülerek daha hızlı olabilir. Yapana kadar, kodumuzun hızlı bir şekilde çalışmasını istediğimizde bunlardan kaçınırız.


3

HotSpot, hepsi satır içi olduğu sürece, sistem tarafından oluşturulan istisnalar için istisna kodunu kaldırabilir. Ancak, açıkça oluşturulmuş istisna ve başka şekilde kaldırılmamış olanlar yığın izlemesi oluşturmak için çok zaman harcarlar. fillInStackTraceBunun performansı nasıl etkileyebileceğini görmek için geçersiz kılın .


2

Bir istisna atmak yavaş olmasa bile, normal program akışı için istisnalar atmak hala kötü bir fikirdir. Bu şekilde kullanıldığında, bir GOTO'ya benzer ...

Sanırım bu soruya gerçekten cevap vermiyor. İstisnaları yavaşlatmanın 'geleneksel' bilgeliğinin önceki java sürümlerinde doğru olduğunu hayal edebilirim (<1.4). Bir istisna oluşturmak için VM'nin yığın izlemesinin tamamını oluşturması gerekir. O zamandan bu yana VM'de işleri hızlandırmak için çok şey değişti ve bu muhtemelen geliştirilmiş bir alan.


1
"Normal program akışını" tanımlamak iyi olur. İşaretli istisnaları bir iş süreci başarısızlığı ve kurtarılamayan arızalar için kontrol edilemeyen bir istisna olarak kullanma hakkında çok şey yazılmıştır, bu nedenle bir anlamda iş mantığındaki bir başarısızlık normal akış olarak düşünülebilir.
Spencer Kormos

2
@Spencer K: Bir istisna, adından da anlaşılacağı gibi, istisnai bir durumun keşfedildiği anlamına gelir (bir dosya gitti, bir ağ aniden kapandı, ...). Bu durumun BEKLENMEDİĞİ anlamına gelir. Durumun ortaya çıkması beklenirse, bunun için bir istisna kullanmam.
Mecki

2
@Mecki: doğru. Geçenlerde bu konuda biriyle görüştüm ... Doğrulama çerçevesi yazıyorlardı ve doğrulama başarısızlığı durumunda bir istisna atıyorlardı. Bence bu oldukça yaygın bir fikir. Yöntemin bir ValidationResult döndürmesini tercih ederim.
user38051

2
Kontrol akışı açısından, bir istisna a breakya da returndeğil ile aynıdır goto.
Hot Licks

3
Tonlarca programlama paradigması var. Bununla ne demek isterseniz, tek bir “normal akış” olamaz. Temel olarak, istisna mekanizması, mevcut kareyi hızla terk etmenin ve yığını belirli bir noktaya kadar çözmenin bir yoludur. “İstisna” kelimesi “beklenmedik” doğası hakkında hiçbir şey ifade etmez. Kısa bir örnek: yönlendirme yolu boyunca belirli koşullar oluştuğunda 404'leri web uygulamalarından “atmak” çok doğaldır. Neden bu mantık istisnalarla uygulanmayacak? Anti-desen nedir?
vücut bulmuş

2

Diyelim ki Integer.parseInt öğesini, istisna atmak yerine ayrıştırılamaz veriler durumunda varsayılan bir değer döndüren aşağıdaki yöntemle diyelim:

  public static int parseUnsignedInt(String s, int defaultValue) {
    final int strLength = s.length();
    if (strLength == 0)
      return defaultValue;
    int value = 0;
    for (int i=strLength-1; i>=0; i--) {
      int c = s.charAt(i);
      if (c > 47 && c < 58) {
        c -= 48;
        for (int j=strLength-i; j!=1; j--)
          c *= 10;
        value += c;
      } else {
        return defaultValue;
      }
    }
    return value < 0 ? /* übergebener wert > Integer.MAX_VALUE? */ defaultValue : value;
  }

Her iki yöntemi de "geçerli" verilere uyguladığınız sürece, her ikisi de yaklaşık olarak aynı oranda çalışır (Integer.parseInt daha karmaşık verileri işlemeyi başarsa bile). Ancak geçersiz verileri ayrıştırmaya çalıştığınız anda (örn. "Abc" 1.000.000 kez ayrıştırmak için), performans farkı önemli olmalıdır.


2

İstisna performansı hakkında harika bir yazı:

https://shipilev.net/blog/2014/exceptional-performance/

Yığın izlemeli ve içermeyen vb.

Benchmark                            Mode   Samples         Mean   Mean error  Units

dynamicException                     avgt        25     1901.196       14.572  ns/op
dynamicException_NoStack             avgt        25       67.029        0.212  ns/op
dynamicException_NoStack_UsedData    avgt        25       68.952        0.441  ns/op
dynamicException_NoStack_UsedStack   avgt        25      137.329        1.039  ns/op
dynamicException_UsedData            avgt        25     1900.770        9.359  ns/op
dynamicException_UsedStack           avgt        25    20033.658      118.600  ns/op

plain                                avgt        25        1.259        0.002  ns/op
staticException                      avgt        25        1.510        0.001  ns/op
staticException_NoStack              avgt        25        1.514        0.003  ns/op
staticException_NoStack_UsedData     avgt        25        4.185        0.015  ns/op
staticException_NoStack_UsedStack    avgt        25       19.110        0.051  ns/op
staticException_UsedData             avgt        25        4.159        0.007  ns/op
staticException_UsedStack            avgt        25       25.144        0.186  ns/op

Yığın izinin derinliğine bağlı olarak:

Benchmark        Mode   Samples         Mean   Mean error  Units

exception_0000   avgt        25     1959.068       30.783  ns/op
exception_0001   avgt        25     1945.958       12.104  ns/op
exception_0002   avgt        25     2063.575       47.708  ns/op
exception_0004   avgt        25     2211.882       29.417  ns/op
exception_0008   avgt        25     2472.729       57.336  ns/op
exception_0016   avgt        25     2950.847       29.863  ns/op
exception_0032   avgt        25     4416.548       50.340  ns/op
exception_0064   avgt        25     6845.140       40.114  ns/op
exception_0128   avgt        25    11774.758       54.299  ns/op
exception_0256   avgt        25    21617.526      101.379  ns/op
exception_0512   avgt        25    42780.434      144.594  ns/op
exception_1024   avgt        25    82839.358      291.434  ns/op

Diğer ayrıntılar için (JIT'ten x64 birleştirici dahil) orijinal blog gönderisini okuyun.

Bu, Hazırda Beklet / İlkbahar / etc-EE-bokunun istisnalar (xD) ve uygulama kontrol akışının istisnalardan uzakta yeniden yazılması ( yöntem çağrısındaki C'deki gibi continure/ breakve dönen booleanbayraklarla değiştirilmesi) nedeniyle yavaş olduğu anlamına gelir . Uygulamanızın performansını artırır 10x-100x , onları ne sıklıkta attığınıza bağlı olarak))


0

Ben sadece bir istisna hiçbir şey ile değiştiremezsiniz gibi method1 bir boolean ve çağıran yönteme bir onay dönmek için yukarıdaki @Mecki yanıtını değiştirdim. İki çalışmadan sonra, yöntem1 hala en hızlı veya yöntem2 kadar hızlıydı.

Kodun anlık görüntüsü:

// Calculates without exception
public boolean method1(int i) {
    value = ((value + i) / i) << 1;
    // Will never be true
    return ((i & 0xFFFFFFF) == 1000000000);

}
....
   for (i = 1; i < 100000000; i++) {
            if (t.method1(i)) {
                System.out.println("Will never be true!");
            }
    }

ve sonuçlar:

Koşu 1

method1 took 841 ms, result was 2
method2 took 841 ms, result was 2
method3 took 85058 ms, result was 2

Koşu 2

method1 took 821 ms, result was 2
method2 took 838 ms, result was 2
method3 took 85929 ms, result was 2

0

Bir istisna , yalnızca çalışma zamanında beklenmeyen durumlarla başa çıkmak içindir.

Derleme zamanında yapılabilecek basit doğrulama yerine bir istisna kullanılması, doğrulama işlemini yürütme süresine kadar geciktirir. Bu da programın verimliliğini azaltacaktır.

Basit bir if..else doğrulaması kullanmak yerine bir istisna atmak da kodun yazılmasını ve bakımını karmaşık hale getirir.


-3

Programlı olarak verilerin kontrol edilmesine karşı İstisna hız hakkındaki fikrim.

Birçok sınıfta String to value dönüştürücü (tarayıcı / ayrıştırıcı), saygın ve tanınmış kütüphaneler vardı;)

genellikle formu vardır

class Example {
public static Example Parse(String input) throws AnyRuntimeParsigException
...
}

istisna adı sadece örnektir, genellikle işaretlenmez (çalışma zamanı), bu nedenle atar bildirim sadece benim resmim

bazen ikinci form vardır:

public static Example Parse(String input, Example defaultValue)

asla fırlatma

İkincisi mevcut değilse (veya programcı çok daha az doküman okur ve yalnızca önce kullanırsa), bu kodu normal ifadeyle yazın. Düzenli ifade havalı, politik olarak doğru vb.

Xxxxx.regex(".....pattern", src);
if(ImTotallySure)
{
  Example v = Example.Parse(src);
}

bu kodla programcıların istisna maliyeti yoktur. AMA normal ifadelerin çok YÜKSEK maliyetiyle DAİMA bazen küçük istisna maliyetine kıyasla karşılaştırılabilir.

Neredeyse her zaman bu bağlamda kullanıyorum

try { parse } catch(ParsingException ) // concrete exception from javadoc
{
}

stacktrace vb. analiz etmeden, sizlerin derslerinin oldukça hızlı olduğuna inanıyorum.

Korkma İstisnalar


-5

İstisnalar neden normal getirilerden daha yavaş olmalıdır?

Yığın izini terminale yazdırmadıkça, bir dosyaya veya benzer bir şeye kaydedin, catch-block diğer kod bloklarından daha fazla iş yapmaz. Bu yüzden, "yeni my_cool_error () atmak" ın neden bu kadar yavaş olması gerektiğini düşünemiyorum.

Güzel soru ve bu konuda daha fazla bilgi için sabırsızlanıyorum!


17
İstisna, gerçekten kullanılmasa bile yığın izlemesi hakkındaki bilgileri yakalamak zorundadır.
Jon Skeet
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.