簡體   English   中英

即使從未引發異常,使用try-catch塊是否昂貴?

[英]Is it expensive to use try-catch blocks even if an exception is never thrown?

我們知道捕獲異常非常昂貴。 但是,即使從不拋出異常,在Java中使用try-catch塊是否也很昂貴?

我發現了堆棧溢出問題/答案為什么嘗試塊價格昂貴? ,但它適用於.NET

try幾乎沒有任何花費。 無需在運行時進行try ,而是在編譯時對代碼的元數據進行結構化,這樣,當引發異常時,它現在執行相對昂貴的操作,即遍歷堆棧並查看是否存在任何try塊捕獲此異常。 從外行的角度來看, try可能也是免費的。 它實際上是在拋出使您付出代價的異常-但除非您拋出數百或數千個異常,否則您仍然不會注意到成本。


try會帶來一些小費用。 Java無法對try塊中的代碼進行某些其他方面的優化。 例如,Java經常會重新安排方法中的指令以使其運行更快-但是Java還需要保證,如果引發異常,則將觀察該方法的執行,就像執行源代碼中編寫的語句一樣為了達到某條線。

因為在try塊中可以引發異常(在try塊的任何行上!某些異常是異步引發的,例如通過在Thread上調用stop (已棄用),甚至OutOfMemoryError幾乎可以在任何地方發生),並且但是它可以被捕獲,並且代碼可以使用相同的方法繼續執行,因此很難對可以進行的優化進行推理,因此不太可能發生優化。 (有些人必須對編譯器進行編程,以進行推理,保證正確性等。對於那些意為“例外”的事情,這將是一個巨大的痛苦。)但是,實際上,您不會注意到這樣的事情。

我們來衡量一下吧?

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);
        }
    }
}

在我的計算機上,打印的內容如下:

try     0.598 ns
no try  0.601 ns

至少在這個簡單的示例中,try語句對性能沒有可測量的影響。 隨意測量更復雜的參數。

一般來說,我建議您不要擔心語言構造的性能成本,除非您有證據證明代碼中存在實際的性能問題。 或正如Donald Knuth 所說 :“過早的優化是萬惡之源”。

try / catch可能會對性能產生一些影響。 這是因為它阻止JVM進行一些優化。 約書亞·布洛赫(Joshua Bloch)在《有效的Java》中說:

•將代碼放在try-catch塊中會禁止現代JVM實現可能執行的某些優化。

是的,正如其他人所說, try塊會禁止圍繞其周圍的{}字符進行某些優化。 特別是,優化器必須假設該塊內的任何點都可能發生異常,因此無法保證語句會被執行。

例如:

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

如果不進行try ,則可以將計算得出的分配給x的值另存為“公共子表達式”,然后重新使用以分配給y 但是由於try ,無法保證第一個表達式曾經被求值,因此必須重新計算該表達式。 在“直線”代碼中,這通常不是什么大問題,但在循環中可能很重要。

但是應注意,這僅適用於JITCed代碼。 javac僅進行少量優化,字節碼解釋器進入/離開try塊的成本為零。 (沒有生成字節碼來標記塊邊界。)

為了最好的:

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

輸出:

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

javap輸出:

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”。

要了解為什么無法執行優化,了解底層機制很有用。 我能找到的最簡潔的示例是在C宏中實現的: 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)

編譯器通常很難確定是否可以將跳轉本地化為X,Y和Z,因此它們會跳過無法保證安全的優化,但是實現本身比較輕便。

另一個微基准測試( 來源 )。

我創建了一個測試,其中我根據異常百分比來衡量try-catch和no-try-catch代碼的版本。 10%百分比表示10%的測試用例被零個案例除。 在一種情況下,它由try-catch塊處理,在另一種情況下,由條件運算符處理。 這是我的結果表:

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

也就是說,這些情況之間沒有顯着差異。

我發現捕獲NullPointException非常昂貴。 對於1.2k的操作,當我使用if(object==null)進行同樣的處理時,時間分別為200ms和12ms,這對我來說是一個很大的進步。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM