2009-02-19 34 views
20

Bạn có biết cách ném và xử lý ngoại lệ đắt tiền trong java không?Các trường hợp ngoại lệ là bao nhiêu Tiền mặt

Chúng tôi có một số cuộc thảo luận về chi phí thực của trường hợp ngoại lệ trong nhóm của chúng tôi. Một số tránh chúng thường xuyên nhất có thể, một số người nói rằng việc mất hiệu năng bằng cách sử dụng các ngoại lệ được đánh giá cao.

Hôm nay tôi tìm thấy những đoạn mã sau đây trong phần mềm của chúng tôi:

private void doSomething() 
{ 
    try 
    { 
     doSomethingElse(); 
    } 
    catch(DidNotWorkException e) 
    { 
     log("A Message"); 
    } 
    goOn(); 
} 
private void doSomethingElse() 
{ 
    if(isSoAndSo()) 
    { 
     throw new DidNotWorkException(); 
    } 
    goOnAgain(); 
} 

Làm thế nào là thực hiện nay so với

private void doSomething() 
{ 
    doSomethingElse(); 
    goOn(); 
} 
private void doSomethingElse() 
{ 
    if(isSoAndSo()) 
    { 
     log("A Message"); 
     return; 
    } 
    goOnAgain(); 
} 

Tôi không muốn thảo luận đang thẩm mỹ hoặc bất cứ điều gì, nó chỉ là về hành vi thời gian chạy! Bạn có kinh nghiệm/đo lường thực tế không?

+1

Đặc biệt tốn kém. –

Trả lời

6

tôi đã không làm phiền để đọc lên vào Exceptions nhưng làm một bài kiểm tra rất nhanh chóng với một số mã sửa đổi của bạn tôi đi đến kết luận rằng trường hợp ngoại lệ khá chậm hơn rất nhiều so với trường hợp boolean.

tôi có kết quả như sau:

Exception:20891ms 
Boolean:62ms 

Từ mã này:

public class Test { 
    public static void main(String args[]) { 
      Test t = new Test(); 
      t.testException(); 
      t.testBoolean(); 
    } 
    public void testException() { 
      long start = System.currentTimeMillis(); 
      for(long i = 0; i <= 10000000L; ++i) 
        doSomethingException(); 
      System.out.println("Exception:" + (System.currentTimeMillis()-start) + "ms"); 
    } 
    public void testBoolean() { 
      long start = System.currentTimeMillis(); 
      for(long i = 0; i <= 10000000L; ++i) 
        doSomething(); 
      System.out.println("Boolean:" + (System.currentTimeMillis()-start) + "ms"); 
    } 

    private void doSomethingException() { 
     try { 
      doSomethingElseException(); 
     } catch(DidNotWorkException e) { 
      //Msg 
     } 
    } 
    private void doSomethingElseException() throws DidNotWorkException { 
     if(!isSoAndSo()) { 
      throw new DidNotWorkException(); 
     } 
    } 
    private void doSomething() { 
     if(!doSomethingElse()) 
      ;//Msg 
    } 
    private boolean doSomethingElse() { 
     if(!isSoAndSo()) 
      return false; 
     return true; 
    } 
    private boolean isSoAndSo() { return false; } 
    public class DidNotWorkException extends Exception {} 
} 

tôi dại dột đã không đọc mã của tôi cũng đủ và trước đó đã có một lỗi trong nó (cách lúng túng), nếu ai đó có thể gấp ba lần kiểm tra mã này tôi sẽ rất nhiều appriciate nó, chỉ trong trường hợp tôi đang già đi.

đặc điểm kỹ thuật của tôi là:

  • Biên soạn và chạy trên 1.5.0_16
  • Sun JVM
  • WinXP SP3
  • Intel Centrino Duo T7200 (2.00Ghz, 977Mhz)
  • 2.00 GB Ram

Theo ý kiến ​​của bạn, bạn nên lưu ý rằng ngoại lệ không được đáp ứng hods không cung cấp lỗi nhật ký trong doSomethingElse nhưng thay vào đó trả về boolean để mã gọi có thể xử lý lỗi. Nếu có nhiều vùng mà điều này có thể thất bại thì việc ghi nhật ký lỗi bên trong hoặc ném một ngoại lệ có thể là cần thiết.

+0

Bạn có chắc chắn, bạn đã thử nghiệm với boolean riêng làSoAndSo() {return ** false **; } Trong trường hợp đó, tôi sẽ ngạc nhiên về kết quả của bạn. –

+0

Ôi trời ơi, bạn biết đấy, tôi đang có một trong những ngày đẫm máu đó: (Kết quả tôi đưa ra là đúng, nếu ngoại lệ không được ném có sự khác biệt 2x. –

+0

Không có vấn đề và cảm ơn cho tất cả công việc của bạn về câu hỏi này –

11

Exceptions không được tự do ... vì vậy chúng :-) đắt

Cuốn sách bao gồm Effective Java này một cách chi tiết tốt.

  • Mục 39 Chỉ sử dụng ngoại lệ cho các điều kiện đặc biệt.
  • mục 40 Sử dụng ngoại lệ đối với điều kiện thu hồi

Tác giả thấy rằng trường hợp ngoại lệ dẫn đến mã tunning 70 lần chậm hơn đối với trường hợp thử nghiệm của mình trên máy tính của mình với đặc biệt VM và hệ điều hành kết hợp của mình.

+0

Sai lầm hợp lý - Chúng chỉ có thể rẻ! – alex

+0

Không ... chúng không phải là :-) (nhưng tôi phải tìm báo giá ...) – TofuBeer

+0

Trường hợp ngoại lệ rất tốn kém. Tôi đã tạo ra một điểm chuẩn mà trường hợp ngoại lệ chỉ xảy ra trong 1% các cuộc gọi, đó là một trường hợp thực tế hơn hoặc ít hơn. Ngay cả trong những trường hợp này, hình phạt hiệu suất rất cao, không phải 70 lần, nhưng vẫn còn rất cao. Xem mã nguồn và kết quả benchmark [ở đây] (http://www.jquantlib.org/index.php/Cost_of_Exceptions_in_Java). –

3

Tôi không có phép đo thực sự, nhưng việc ném ngoại lệ thì đắt hơn.

Ok, đây là một liên kết liên quan đến khuôn khổ NET, nhưng tôi nghĩ rằng cùng áp dụng cho Java cũng như:

exceptions & performance

Điều đó nói rằng, bạn không nên ngần ngại sử dụng chúng khi thích hợp . Đó là: không sử dụng chúng để kiểm soát dòng chảy, nhưng sử dụng chúng khi có điều gì đó đặc biệt vui vẻ; một cái gì đó mà bạn không mong đợi xảy ra.

+0

Hoàn toàn đúng - nếu bạn sử dụng ngoại lệ cho các trường hợp ngoại lệ, bạn vẫn ổn. Nếu bạn ném vài giây một lần, nó sẽ làm chậm bạn xuống, bởi vì chúng không được thiết kế để điều khiển luồng như thế. – ojrac

+0

Hmm, tại sao hạ cấp? Nếu câu trả lời bị hạ cấp, bạn nên động viên tại sao. –

3

Tôi nghĩ nếu chúng ta sử dụng ngoại lệ khi cần thiết (điều kiện đặc biệt), lợi ích vượt xa mọi hình phạt về hiệu suất mà bạn có thể phải trả. Tôi nói có thể kể từ khi chi phí thực sự là một chức năng của tần số mà các ngoại lệ được ném trong ứng dụng đang chạy. Trong ví dụ bạn đưa ra, có vẻ như thất bại không phải là bất ngờ hoặc thảm họa, vì vậy phương pháp thực sự phải trả về một bool để báo hiệu trạng thái thành công của nó thay vì sử dụng ngoại lệ, do đó làm cho chúng trở thành một phần của luồng điều khiển thông thường.
Trong một vài cải tiến về hiệu suất hoạt động mà tôi đã tham gia, chi phí ngoại lệ là khá thấp. Bạn sẽ dành nhiều thời gian hơn để cải thiện sự phức tạp của các hoạt động phổ biến, lặp lại cao.

4

Đây vốn là JVM cụ thể, vì vậy bạn không nên tin tưởng một cách mù quáng bất kỳ lời khuyên nào được đưa ra, nhưng thực sự đo lường trong tình huống của bạn. Nó không phải là khó khăn để tạo ra một "ném một triệu ngoại lệ và in ra sự khác biệt của System.currentTimeMillis" để có được một ý tưởng thô.

Đối với đoạn mã bạn liệt kê, tôi sẽ yêu cầu tác giả gốc ghi lại lý do tại sao anh ấy sử dụng ngoại lệ ném ở đây vì nó không phải là "đường dẫn ít ngạc nhiên nhất".

(Bất cứ khi nào bạn làm điều gì đó một cách phức tạp, bạn sẽ làm cho công việc không cần thiết phải được thực hiện bởi người đọc để hiểu tại sao bạn làm như vậy thay vì cách thông thường - công việc đó phải được hợp lý theo ý kiến ​​của tôi tác giả giải thích một cách cẩn thận lý do tại sao nó được thực hiện như vậy vì có PHẢI là một lý do).

Trường hợp ngoại lệ là một công cụ rất hữu ích, nhưng chỉ nên được sử dụng khi cần thiết :)

+1

+1 dành riêng cho JVM. Có những vấn đề hiệu năng lớn với các khối try-catch trong Java 1.4 chẳng hạn (hầu hết được sửa chữa trong các phiên bản sau). – cletus

+0

+1 cho JVM cụ thể và thực sự kiểm tra nó. Java đã đi một chặng đường dài về tốc độ. – Kiv

0

Tôi nghĩ rằng bạn đang yêu cầu điều này từ góc hơi sai. Ngoại lệ được thiết kế để được sử dụng để báo hiệu trường hợp ngoại lệ và dưới dạng cơ chế dòng chương trình cho những trường hợp đó. Vì vậy, câu hỏi mà bạn nên hỏi là, liệu "logic" của lệnh gọi mã cho các trường hợp ngoại lệ.

Ngoại lệ thường được thiết kế để hoạt động tốt trong quá trình sử dụng. Nếu chúng được sử dụng theo cách mà chúng là nút cổ chai, thì trên tất cả, đó có thể là dấu hiệu cho thấy chúng chỉ được sử dụng cho "điều sai" toàn bộ stop-- tức là những gì bạn có là một chương trình thiết kế vấn đề thay vì hiệu suất .

Ngược lại, nếu ngoại lệ dường như bị "sử dụng cho đúng thứ" thì điều đó có thể có nghĩa là nó cũng sẽ hoạt động OK.

11

Phần chậm nhất trong trường hợp ngoại lệ là filling in the stack trace.

Nếu bạn tạo trước ngoại lệ và sử dụng lại nó, JIT có thể tối ưu hóa nó xuống "a machine level goto".

Tất cả những gì đã được nói, trừ khi mã từ câu hỏi của bạn ở trong vòng lặp thực sự chặt chẽ, sự khác biệt sẽ không đáng kể.

+0

Có thể bạn có thể tạo các ngoại lệ mới mà không lưu trữ nó nếu hàm tạo ngoại lệ của bạn gọi super (msg, nguyên nhân,/* enableSuppression */false,/* writableStackTrace */false); – giampaolo

3

Cảm ơn tất cả các câu trả lời.

Cuối cùng tôi đã theo đề xuất của Thorbjørn và viết một chương trình thử nghiệm nhỏ, đo lường hiệu suất của bản thân. Kết quả là: Không khác biệt giữa hai biến thể (trong các vấn đề về hiệu suất).

Mặc dù tôi không hỏi về thẩm mỹ mã hoặc một cái gì đó, tức là ý định ngoại lệ là gì ... hầu hết các bạn cũng đề cập đến chủ đề đó. Nhưng trong thực tế mọi thứ không phải luôn luôn rõ ràng ... Trong trường hợp xem xét mã đã được sinh ra một thời gian dài trước đây khi tình hình trong đó các ngoại lệ được ném dường như là một trong những đặc biệt. Hôm nay thư viện được sử dụng khác nhau, hành vi và cách sử dụng của các ứng dụng khác nhau thay đổi, độ che phủ thử nghiệm không tốt lắm, nhưng mã vẫn thực hiện công việc, chỉ hơi chậm một chút (Đó là lý do tại sao tôi yêu cầu hiệu suất !!). Trong tình huống đó, tôi nghĩ, nên có một lý do chính đáng để thay đổi từ A sang B, theo ý kiến ​​của tôi, không thể là "Đó không phải là những ngoại lệ được đưa ra!".

Hóa ra việc ghi nhật ký ("Thư") là (so với mọi thứ khác xảy ra) rất tốn kém, vì vậy tôi nghĩ, tôi sẽ loại bỏ điều này.

EDIT:

Các mã kiểm tra hoàn toàn giống với một trong các bài bản gốc, được gọi bằng một phương pháp testPerfomance() trong một vòng lặp được bao quanh bởi System.currentTimeMillis() -calls để có được thời gian thực hiện ... nhưng:

Tôi đã xem xét mã kiểm tra ngay bây giờ, xoay mọi thứ khác (báo cáo nhật ký) và lặp lại gấp 100 lần so với trước đây và hóa ra bạn tiết kiệm được 4,7 giây cho một triệu cuộc gọi khi sử dụng B thay vì A từ bài gốc. Như Ron đã nói fillStackTrace là phần đắt nhất (+1 cho điều đó) và bạn có thể tiết kiệm gần như nhau (4,5 giây) nếu bạn ghi đè lên nó (trong trường hợp bạn không cần nó, như tôi). Tất cả trong tất cả nó vẫn là một sự khác biệt gần như bằng không trong trường hợp của tôi, kể từ khi mã được gọi là 1000 lần một giờ và các phép đo cho thấy tôi có thể tiết kiệm 4,5 triệu trong thời gian đó ...

Vì vậy, câu trả lời đầu tiên của tôi ở trên là một chút sai lầm, nhưng những gì tôi nói về việc cân bằng lợi ích chi phí của việc tái cấu trúc vẫn còn đúng.

+0

Trong thử nghiệm của bạn, làm thế nào ofen là ngoại lệ ném? Mỗi lần hoặc rất ít lần? – TofuBeer

+0

Bạn có thể đăng mã cho các bài kiểm tra của mình không? Cũng như JVM và hệ điều hành bạn đang ở trên? (Tôi thấy khó mà tin rằng thời gian bằng không hoặc rất nhỏ vì hầu hết các nhà cung cấp JVM sẽ không bận tâm tối ưu hóa trường hợp ngoại lệ). – TofuBeer

+0

oh ... và nếu điều này là trong mã sản xuất mà bạn đang nhìn thấy không có thay đổi về tốc độ ... 1) tại sao bạn ném một ngoại lệ mỗi khi một phương pháp được gọi là? 2) mức độ thường xuyên được gọi (nếu không thường xuyên thì thời gian sẽ không lớn) – TofuBeer

8

Phần chậm về ngoại lệ là tạo dấu vết ngăn xếp (trong hàm tạo của java.lang.Throwable), phụ thuộc vào độ sâu ngăn xếp. Ném vào chính nó không phải là chậm.

Sử dụng ngoại lệ để báo hiệu lỗi. Tác động hiệu suất sau đó là không đáng kể và theo dõi ngăn xếp giúp xác định nguyên nhân lỗi.

Nếu bạn cần ngoại lệ cho luồng điều khiển (không được khuyến nghị), và lược tả cho thấy ngoại lệ là nút cổ chai, sau đó tạo phân lớp ngoại lệ ghi đè fillInStackTrace() với triển khai trống. Ngoài ra (hoặc bổ sung) nhanh chóng chỉ có một ngoại lệ, lưu trữ nó trong một lĩnh vực và luôn luôn ném cùng một trường hợp.

Sau đây chứng minh trường hợp ngoại lệ mà không ngăn xếp dấu vết bằng cách thêm một phương pháp đơn giản để chuẩn micro (albeit flawed) trong accepted answer:

public class DidNotWorkException extends Exception { 
    public Throwable fillInStackTrace() { 
     return this; 
    } 
} 

Chạy nó bằng cách sử dụng JVM trong -server chế độ (phiên bản 1.6.0_24 trên Windows 7) kết quả bằng:

Exception:99ms 
Boolean:12ms 

Exception:92ms 
Boolean:11ms 

Sự khác biệt đủ nhỏ để có thể bỏ qua trong thực tế.

+0

@Dave Jarvis - nếu đó là hai lần chạy riêng biệt và kết quả đại diện, tôi KHÔNG gọi 900% thời gian thực hiện "hầu như không có sự khác biệt" ... Nếu tất cả chúng ta đều viết mã Hello Worlds, thì hãy phát điên với ngoại lệ ... –

+1

Điều đó thay vì trông giống như một yếu tố của 10 thay vì hầu như không một sự khác biệt. Nếu bạn sử dụng nó ở nhiều nơi, điều này sẽ làm tổn thương rất nhiều. –

0

Giả sử ngoại lệ sẽ không xảy ra khi cố gắng thực thi câu 1 và 2. Có bất kỳ lần truy cập hiệu suất nào giữa hai mã mẫu không?

Nếu không, điều gì sẽ xảy ra nếu phương pháp DoSomething() phải thực hiện huuuge lượng công việc (tải các cuộc gọi đến các phương pháp khác, v.v ...)?

1:

try 
{ 
    DoSomething(); 
} 
catch (...) 
{ 
    ... 
} 

2:

DoSomething(); 
+0

trong Java thử là miễn phí - ném chi phí (và cố gắng không thực sự miễn phí ... nhưng hãy quên tôi nói rằng :-) Vì vậy, nếu ngoại lệ không được ném thì không có tốc độ nhấn. Một phần của logic là ném không nên được sử dụng để kiểm soát dòng chảy vì vậy nó không được tối ưu hóa - nó là cho các trường hợp bất thường không phải là một trong những phổ biến. – TofuBeer

Các vấn đề liên quan