2011-01-29 48 views
4

使用異常處理正常的代碼流很糟糕 - 速度很慢,代碼質量很差,從第1天起,我們都會把這種情況帶入我們的頭腦中。至少我有!這也是有道理的,異常每次調用時都會生成堆棧跟蹤,堆棧跟蹤需要很長時間才能生成,因此拋出和捕獲的異常比等效的if語句慢得多。Java中異常的速度

所以我決定做一個快速的例子來證明這一點。

public static void main(String[] args) throws Exception { 
    Object[] objs = new Object[500000]; 
    for (int i = 0; i < objs.length; i++) { 
     if (Math.random() < 0.5) { 
      objs[i] = new Object(); 
     } 
    } 
    long cur = System.currentTimeMillis(); 
    for (Object o : objs) { 
     try { 
      System.out.print(o.getClass()); 
     } 
     catch (Exception e) { 
      System.out.print("null"); 
     } 
    } 
    System.err.println("Try/catch: "+(System.currentTimeMillis() - cur)); 
    cur = System.currentTimeMillis(); 
    for (Object o : objs) { 
     if(o==null) { 
      System.out.print("null"); 
     } 
     else { 
      System.out.print(o.getClass()); 
     } 
    } 
    System.err.println("If: "+(System.currentTimeMillis() - cur)); 
} 

然而,上運行的代碼,我吃驚地看到以下內容:

Try/catch: 11204 
If: 11953 

我再次運行代碼,此時 「如果」 是快:

Try/catch: 12188 
If: 12187 

。 ..但只有一個毫秒。

這是所有在服務器虛擬機上運行的,我知道大部分時間都會被print()語句佔用,有些運行會顯示速度比try/catch快。但無論如何,他們當然不應該接近?那麼如何生成堆棧跟蹤比單個if語句更快呢?

編輯:爲了澄清,這個問題是一個純粹的學術 - 我知道使用正常代碼流的異常是很好,絕對不會這樣做!

回答

4

這個最近被討論在Java專家newletter:http://www.javaspecialists.eu/archive/Issue187.html

簡短的回答:這段時間不產生堆棧跟蹤每一次,但緩存異常的一個實例。

+0

謝謝,該通訊解釋得相當好。我以前看過它,但沒有那篇文章! – berry120 2011-01-30 00:26:06

0

幾件事情

  • 基準測試是嚴重的缺陷。
  • 捕捉/拋出Java異常的速度很快,非常快
  • 創建和收集堆棧跟蹤比較慢(但熱點簽註堆棧)

話雖這麼說:異常是不慢。打印堆棧跟蹤是另一個問題。

額外的好處:null檢查的速度快,因此是無效的陷阱不使用異常

2

因爲異常拋出點和支撐點是在相同的範圍內時,與JIT不能證明你實際查看堆棧跟蹤,可以將異常優化爲單個分​​支。您可以通過顯式拋出自定義類型的異常(理想情況下跨函數調用邊界)來避免這種情況。爲了控制分配成本,在非拋出的情況下,確保無論如何構建一個所述異常對象的實例(並理想地返回它,以確保逃逸分析不能將其轉換爲堆棧分配)。

這就是說,JIT可以做各種奇怪的事情。最好嘗試一個與Real Code儘可能相似的測試用例,而不是像這樣的仿真例子,所以在任何優化之後,您都可以看到JIT可以應用於真實代碼的基準測試結果。

1

我相信早期版本的Java中堆棧跟蹤過去非常緩慢。事實上,我編寫了一個程序,其中包含英國網格地圖1000 x 800,並使用if語句,該程序在10分鐘內運行,在異常情況下運行並捕獲,並在2小時內運行。這是十年前。

最近,JVM在處理try-catch方面獲得了更高的效率。

我從一開始就把「例外情況用於特殊情況」捲入了我的行列,當我看到遊戲框架將異常作爲全局goto語句執行控制流程時,我最初感到震驚。但是,在真實的工作場景中運行Play並且速度非常快。這是我完全失去的另一種誤解,並開放了我的想法,以許多使用例外作爲設計模式的新方法。

1

其基本原因是堆棧跟蹤數據的數量現在被推遲到實際需要時,最初在創建異常時填充它。

您可能想要將堆棧跟蹤打印到ByteArrayOutputStream中,以查看堆棧跟蹤實際上的位置使用了

1

值得注意的是,雖然很多人認爲流量控制的異常是「不良形式」,但並不一定比較慢。例如,你可能有

  • 之間做出選擇測試是否存在的值在每次迭代
  • 假設值存在,並且拋出一個異常時,它不

對於當該值存在次數不多,每個迭代版本的測試可能會更快。對於價值更經常存在的情況,失敗的版本可能會更快。無可否認,在兩者之間做出選擇假定您對數據有很好的理解,並且希望能夠運行一個分析器來查看兩者之間切換的影響。所有這些都說了,如果你實際上不知道拋出的異常會更快,那麼使用每次迭代測試可能會更好,因爲它對許多人來說更爲明顯它在做。

+0

我完全同意 - 問題來自純粹的學術觀點。除非有很好的理由,否則我不會主張在正常的代碼流中使用異常! – berry120 2011-01-29 17:33:16

+0

我更加註意到這樣一個概念,即不應該使用異常,因爲它們比較慢。即使它們確實比較慢,在某些情況下,您可能會選擇1慢速和1000快速的事情,並且此時決定會變得不太清晰。 – RHSeeger 2011-01-29 17:38:30

0

但是,速度並不是在正常流程中不使用異常的唯一原因。例外情況是一種機制,表明方法的合同被破壞。如果你開始使用異常作爲流量控制,那麼它就會失敗。通常你可以寫幾行代碼來避免這種異常。