Cuối cùng có đắt không


14

Trong trường hợp mã mà bạn phải thực hiện dọn dẹp tài nguyên trước khi thoát khỏi hàm, có sự khác biệt lớn về hiệu năng giữa 2 cách thực hiện này không.

  1. Làm sạch tài nguyên trước mỗi tuyên bố hoàn trả

    void func()
    {
        login();
    
        bool ret = dosomething();
        if(ret == false)
        {
            logout();
            return;
        }
    
        ret = dosomethingelse();
        if(ret == false)
        {
            logout();
            return;
        }
    
        dootherstuff();
    
        logout();
    }
  2. Làm sạch tài nguyên trong một khối cuối cùng

    void func()
    {
        login();
    
        try
        {
            bool ret = dosomething();
            if(ret == false)
                return;
    
            ret = dosomethingelse();
            if(ret == false)
                return;
    
            dootherstuff();
    
        }
        finally
        {
            logout();
        }
    }

Tôi đã thực hiện một số thử nghiệm cơ bản trong các chương trình mẫu và dường như không có nhiều sự khác biệt. Tôi rất thích finallycách làm này - nhưng tôi đã tự hỏi liệu nó sẽ gây ra bất kỳ hiệu suất nào trong một dự án lớn.


3
Nó không thực sự đáng để đăng như một câu trả lời, nhưng không. Nếu bạn đang sử dụng Java, dự kiến ​​các ngoại lệ sẽ là chiến lược xử lý lỗi mặc định, thì bạn nên sử dụng thử / bắt / cuối cùng - ngôn ngữ được tối ưu hóa cho mẫu bạn đang sử dụng.
Jonathan Rich

1
@JonathanRich: làm sao vậy?
haylem

2
Viết mã sạch theo cách nó thể hiện những gì bạn đang cố gắng thực hiện. Tối ưu hóa sau này khi bạn biết bạn gặp vấn đề - đừng tránh các tính năng ngôn ngữ vì bạn nghĩ rằng bạn có thể loại bỏ một vài phần nghìn giây của thứ gì đó không chậm ở nơi đầu tiên.
Sean McS Something

@mikeTheLiar - Nếu bạn muốn nói rằng tôi nên viết if(!cond), thì đó là java khiến tôi làm điều này. Trong C ++, đó là cách tôi viết mã cả booleans và cho các loại khác - tức là int x; if(!x). Vì java cho phép tôi chỉ sử dụng cái này cho booleans, tôi đã hoàn toàn ngừng sử dụng if(cond)& if(!cond)trong java.
user93353

1
@ user93353 khiến tôi buồn. Tôi muốn nhìn thấy (someIntValue != 0)hơn là so sánh hơn là đánh giá booleans. Điều đó có mùi với tôi, và tôi tái cấu trúc nó ngay lập tức khi tôi nhìn thấy nó trong tự nhiên.
MikeTheLiar

Câu trả lời:


23

Như đã chỉ ra trong các trường hợp ngoại lệ Java chậm như thế nào? người ta có thể thấy rằng sự chậm chạp try {} catch {}nằm trong chính sự xúi giục của ngoại lệ.

Tạo một ngoại lệ sẽ tìm nạp toàn bộ ngăn xếp cuộc gọi từ thời gian chạy và đây là nơi chi phí. Nếu bạn không tạo ra một ngoại lệ, điều này chỉ tăng rất ít thời gian.

Trong ví dụ được đưa ra trong câu hỏi này, không có bất kỳ trường hợp ngoại lệ nào, người ta sẽ không mong đợi bất kỳ sự chậm lại nào khi tạo ra chúng - chúng không được tạo ra. Thay vào đó, những gì ở đây là try {} finally {}để xử lý việc phân bổ tài nguyên trong khối cuối cùng.

Vì vậy, để trả lời câu hỏi, không, không có chi phí thời gian thực trong try {} finally {}cấu trúc không sử dụng ngoại lệ (không phải là chưa từng thấy, như đã thấy). Có gì có thể đắt tiền là thời gian bảo trì khi người ta đọc mã và thấy phong cách mã không điển hình này và các coder có để có được tâm trí của họ xung quanh rằng cái gì khác xảy ra trong phương pháp này sau khi returntrước khi trở về cuộc gọi trước đó.


Như đã đề cập, bảo trì là một đối số cho cả hai cách để làm điều này. Đối với hồ sơ, sau khi xem xét, sở thích của tôi sẽ là phương pháp cuối cùng.

Xem xét thời gian duy trì dạy cho ai đó một cấu trúc ngôn ngữ mới. Nhìn thấy try {} finally {}không phải là thứ mà người ta thường thấy trong mã Java và do đó có thể gây nhầm lẫn cho mọi người. Có một mức độ thời gian bảo trì để tìm hiểu các cấu trúc nâng cao hơn một chút trong Java so với những gì mọi người quen thuộc nhìn thấy.

Các finally {}khối luôn luôn chạy. Và đây là lý do tại sao bạn nên sử dụng nó. Cũng xem xét thời gian duy trì gỡ lỗi của phương pháp không cuối cùng khi ai đó quên bao gồm đăng xuất vào thời điểm thích hợp hoặc gọi nó vào thời điểm không thích hợp hoặc quên trả lại / thoát sau khi gọi nó để được gọi hai lần. Có rất nhiều lỗi có thể xảy ra với điều này mà việc sử dụng try {} finally {}khiến không thể có.

Khi cân nhắc hai chi phí này, sẽ tốn kém trong thời gian bảo trì không sử dụng try {} finally {}phương pháp này. Mặc dù mọi người có thể tinh ranh hơn về việc có bao nhiêu phần nghìn giây hoặc hướng dẫn jvm bổ sung mà try {} finally {}khối được so sánh với phiên bản khác, người ta cũng phải xem xét số giờ dành cho việc gỡ lỗi theo cách giải quyết tài nguyên ít hơn lý tưởng.

Viết mã có thể duy trì trước, và tốt nhất là theo cách sẽ ngăn các lỗi được viết sau đó.


1
Do đó, một chỉnh sửa được đề xuất anon đã nói: "thử / cuối cùng đưa ra các đảm bảo mà bạn sẽ không có liên quan đến ngoại lệ thời gian chạy. Dù bạn có thích hay không, bất kỳ dòng nào cũng có khả năng ném ngoại lệ" - Tôi: điều đó không chính xác . Cấu trúc được đưa ra trong câu hỏi không có bất kỳ ngoại lệ bổ sung nào sẽ làm chậm hiệu suất của mã. Không có tác động hiệu suất bổ sung bằng cách gói một thử / cuối cùng xung quanh khối so với khối không thử / cuối cùng.

2
Điều thậm chí còn tốn kém hơn trong việc bảo trì so với việc hiểu cách sử dụng thử này - cuối cùng, là phải duy trì một số khối dọn dẹp. Bây giờ ít nhất bạn biết rằng nếu cần làm sạch thêm, chỉ có một nơi để đặt nó.
Bart van Ingen Schenau

@BartvanIngenSchenau Thật vậy. Thử cuối cùng có lẽ là cách tốt nhất để xử lý nó, nó không phải là cấu trúc phổ biến mà tôi đã thấy trong mã - mọi người gặp khó khăn khi cố gắng hiểu thử cuối cùng và xử lý ngoại lệ nói chung .. nhưng hãy thử -finaly mà không bắt được? Gì? Mọi người thực sự không hiểu nó . Như bạn đề xuất, tốt hơn là hiểu cấu trúc ngôn ngữ hơn là cố gắng tránh nó và làm mọi thứ kém.

Tôi hầu như muốn làm cho nó biết rằng sự thay thế cho thử - cuối cùng cũng không miễn phí. Tôi ước tôi có thể đưa ra một upvote khác cho sự bổ sung của bạn về chi phí.
Bart van Ingen Schenau

5

/programming/299068/how-slow-are-java-exceptions

Câu trả lời được chấp nhận cho câu hỏi này cho thấy việc gói một lệnh gọi hàm trong khối thử bắt có chi phí ít hơn 5% so với cuộc gọi hàm trần. Trên thực tế, việc ném và bắt ngoại lệ đã khiến cho bộ thực thi tăng vọt lên hơn 66 lần so với lệnh gọi hàm trần. Vì vậy, nếu bạn mong đợi thiết kế ngoại lệ được ném thường xuyên thì tôi sẽ cố gắng tránh nó (trong mã quan trọng hiệu năng được định hình đúng), tuy nhiên nếu tình huống đặc biệt là hiếm thì đó không phải là vấn đề lớn.


3
"nếu tình huống đặc biệt là hiếm" - cũng có một tautology ngay tại đó. Đặc biệt có nghĩa là hiếm và đó chính xác là cách sử dụng ngoại lệ. Không bao giờ nên là một phần của dòng chảy thiết kế hoặc kiểm soát.
Jesse C. Choper

1
Trường hợp ngoại lệ không nhất thiết là hiếm trong thực tế, nó chỉ là tác dụng phụ ngẫu nhiên. Ví dụ: nếu bạn có giao diện người dùng xấu, mọi người có thể gây ra luồng trường hợp sử dụng ngoại lệ thường xuyên hơn luồng thông thường
Esailija

2
Không có ngoại lệ được ném trong mã của câu hỏi.

2
@ JesseC.Slicer Có những ngôn ngữ mà không có gì lạ khi thấy chúng được sử dụng làm kiểm soát dòng chảy. Như một ví dụ khi lặp lại bất cứ thứ gì trong python, iterator sẽ ném một ngoại lệ dừng lại khi nó được thực hiện. Ngoài ra ở OCaml, thư viện tiêu chuẩn của họ dường như rất "vui vẻ", nó ném vào một số lượng lớn các tình huống không hiếm gặp (nếu bạn có bản đồ và cố gắng tìm kiếm thứ gì đó không tồn tại trên bản đồ thì nó sẽ ném ).
ném đá vào

@stonemetal Cũng như một người dùng Python, với KeyError
Izkata

4

Thay vì tối ưu hóa - hãy nghĩ về những gì mã của bạn đang làm.

Thêm khối cuối cùng là một triển khai khác - điều đó có nghĩa là bạn sẽ đăng xuất trên mọi ngoại lệ.

Cụ thể - nếu đăng nhập ném một ngoại lệ, hành vi trong chức năng thứ hai của bạn sẽ khác với lần đầu tiên.

Nếu đăng nhập và đăng xuất không thực sự là một phần của hành vi chức năng thì tôi sẽ đề xuất rằng phương thức này nên thực hiện một điều và một điều tốt:

    void func()
    {
            bool ret = dosomething();
            if(ret == false)
                return;

            ret = dosomethingelse();
            if(ret == false)
                return;

            dootherstuff();

    }

và phải ở trong một chức năng đã được gói gọn trong ngữ cảnh quản lý đăng nhập / đăng xuất vì chúng có vẻ khác về cơ bản với những gì mã chính của bạn đang làm.

Bài đăng của bạn được gắn thẻ là Java mà tôi không quen thuộc lắm nhưng trong .net tôi sẽ sử dụng một khối sử dụng cho việc này:

I E:

    using(var loginContext = CreateLoginContext()) 
    {
            // Do all your stuff
    }

Và bối cảnh đăng nhập có một phương thức Vứt bỏ sẽ đăng xuất và dọn sạch các tài nguyên.

Chỉnh sửa: Tôi đã không thực sự trả lời câu hỏi!

Tôi không có bằng chứng có thể đo lường được nhưng tôi không hy vọng nó sẽ đắt hơn hoặc chắc chắn không đủ đắt để nó xứng đáng được tối ưu hóa sớm.

Tôi sẽ để lại phần còn lại của câu trả lời vì mặc dù không trả lời câu hỏi, tôi nghĩ nó hữu ích cho OP.


1
Để hoàn thành câu trả lời của bạn, Java 7 đã giới thiệu một câu lệnh try-with-resource giống với usingcấu trúc .net của bạn , giả sử rằng tài nguyên trong câu hỏi thực hiện AutoCloseablegiao diện.
haylem

Tôi thậm chí sẽ loại bỏ retbiến này , chỉ được gán hai lần để được kiểm tra một dòng sau đó. Làm cho nó if (dosomething() == false) return;.
ott--

Đồng ý nhưng tôi muốn giữ mã OP theo cách nó được cung cấp.
Michael

@ ott-- Tôi cho rằng mã của OP là đơn giản hóa trường hợp sử dụng của họ - ví dụ: họ có thể có một cái gì đó giống như vậy ret2 = doSomethingElse(ret1), nhưng điều đó không liên quan đến câu hỏi nên đã bị xóa.
Izkata

if (dosomething() == false) ...là mã xấu. Sử dụng nhiều hơnif (!dosomething()) ...
Steffen Heil

1

Giả định: Bạn đang phát triển mã C #.

Câu trả lời nhanh là không có hiệu suất đáng kể nào khi sử dụng các khối try / cuối cùng. Có một hiệu suất hit khi bạn ném và bắt ngoại lệ.

Câu trả lời dài hơn là để xem cho chính mình. Nếu bạn nhìn vào mã CIL cơ bản được tạo ( ví dụ: bộ phản xạ cổng đỏ thì bạn có thể thấy các hướng dẫn CIL cơ bản được tạo và xem tác động theo cách đó.


11
Câu hỏi được gắn thẻ java .
Joachim Sauer

Cũng phát hiện ra! ;)
Michael Shaw

3
Ngoài ra, các phương pháp không được viết hoa, mặc dù đó chỉ có thể là hương vị tốt.
Erik Reppen

vẫn là một câu trả lời tốt nếu câu hỏi là c # :)
PhillyNJ

-2

Như những người khác đã lưu ý, mã Ticky sẽ có kết quả khác nhau, nếu dosomethingelsehoặc dootherstuffném ngoại lệ.

Và vâng, bất kỳ cuộc gọi chức năng nào cũng có thể đưa ra một ngoại lệ, ít nhất là a StackOVerflowError! Mặc dù hiếm khi có thể xử lý chúng một cách chính xác (vì bất kỳ chức năng dọn dẹp nào được gọi là có thể sẽ có cùng một vấn đề, trong một số trường hợp (chẳng hạn như thực hiện các khóa chẳng hạn), điều quan trọng là phải xử lý chính xác ...


2
điều này dường như không cung cấp bất cứ điều gì đáng kể so với 6 câu trả lời trước
gnat
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.