Câu trả lời:
Tôi không phải là một chuyên gia về triển khai ngôn ngữ (vì vậy hãy hiểu điều này với một chút muối), nhưng tôi nghĩ rằng một trong những chi phí lớn nhất là giải nén ngăn xếp và lưu trữ nó để theo dõi ngăn xếp. Tôi nghi ngờ điều này chỉ xảy ra khi ngoại lệ được ném (nhưng tôi không biết), và nếu vậy, điều này sẽ được định cỡ chi phí ẩn mỗi khi một ngoại lệ được ném ... vì vậy nó không giống như bạn chỉ nhảy từ một nơi trong mã này sang mã khác, có rất nhiều thứ đang diễn ra.
Tôi không nghĩ đó là vấn đề miễn là bạn đang sử dụng các ngoại lệ cho hành vi NGOẠI LỆ (vì vậy không phải là đường dẫn thông thường, được mong đợi của bạn thông qua chương trình).
Ba điểm cần thực hiện ở đây:
Thứ nhất, có rất ít hoặc KHÔNG có hình phạt hiệu suất khi thực sự có các khối thử bắt trong mã của bạn. Điều này không nên được cân nhắc khi cố gắng tránh có chúng trong ứng dụng của bạn. Bản hit hiệu suất chỉ phát huy tác dụng khi một ngoại lệ được ném ra.
Khi một ngoại lệ được ném ra ngoài các hoạt động giải nén ngăn xếp, v.v. diễn ra mà những người khác đã đề cập, bạn nên lưu ý rằng một loạt các nội dung liên quan đến thời gian chạy / phản chiếu xảy ra để điền các thành viên của lớp ngoại lệ như dấu vết ngăn xếp đối tượng và các thành viên loại khác nhau, v.v.
Tôi tin rằng đây là một trong những lý do tại sao lời khuyên chung nếu bạn định xây lại ngoại lệ là throw;
thay vì ném lại ngoại lệ hoặc tạo một ngoại lệ mới vì trong những trường hợp đó, tất cả thông tin ngăn xếp đó được thu thập trong khi đơn giản ném nó là tất cả được bảo tồn.
throw new Exception("Wrapping layer’s error", ex);
Bạn đang hỏi về chi phí của việc sử dụng try / catch / last khi các ngoại lệ không được ném ra, hoặc chi phí của việc sử dụng các ngoại lệ để kiểm soát luồng quy trình? Cách thứ hai tương tự như việc sử dụng một thanh thuốc nổ để thắp sáng ngọn nến sinh nhật của trẻ mới biết đi, và chi phí liên quan rơi vào các khu vực sau:
Bạn có thể gặp lỗi trang bổ sung do ngoại lệ được ném ra khi truy cập mã không thường trú và dữ liệu không bình thường trong tập hợp hoạt động của ứng dụng của bạn.
cả hai mục trên thường truy cập mã và dữ liệu "nguội", do đó, lỗi trang cứng có thể xảy ra nếu bạn có áp lực bộ nhớ:
Đối với tác động thực tế của chi phí, điều này có thể thay đổi rất nhiều tùy thuộc vào những gì khác đang diễn ra trong mã của bạn vào thời điểm đó. Jon Skeet có một bản tóm tắt tốt ở đây , với một số liên kết hữu ích. Tôi có xu hướng đồng ý với tuyên bố của anh ấy rằng nếu bạn đi đến điểm mà các ngoại lệ đang ảnh hưởng đáng kể đến hiệu suất của bạn, thì bạn có vấn đề về việc sử dụng các ngoại lệ ngoài hiệu suất.
Theo kinh nghiệm của tôi, chi phí lớn nhất là thực sự đưa ra một ngoại lệ và xử lý nó. Tôi đã từng làm việc trong một dự án nơi mã tương tự như sau được sử dụng để kiểm tra xem ai đó có quyền chỉnh sửa đối tượng nào đó hay không. Phương thức HasRight () này được sử dụng ở mọi nơi trong lớp trình bày và thường được gọi cho 100 đối tượng.
bool HasRight(string rightName, DomainObject obj) {
try {
CheckRight(rightName, obj);
return true;
}
catch (Exception ex) {
return false;
}
}
void CheckRight(string rightName, DomainObject obj) {
if (!_user.Rights.Contains(rightName))
throw new Exception();
}
Khi cơ sở dữ liệu thử nghiệm đầy đủ hơn với dữ liệu thử nghiệm, điều này dẫn đến sự chậm lại rất rõ ràng trong khi mở các biểu mẫu mới, v.v.
Vì vậy, tôi đã cấu trúc lại nó như sau, mà - theo các phép đo nhanh hơn sau này - nhanh hơn khoảng 2 bậc độ lớn:
bool HasRight(string rightName, DomainObject obj) {
return _user.Rights.Contains(rightName);
}
void CheckRight(string rightName, DomainObject obj) {
if (!HasRight(rightName, obj))
throw new Exception();
}
Vì vậy, trong ngắn hạn, sử dụng ngoại lệ trong luồng quy trình bình thường chậm hơn khoảng hai bậc độ lớn sau đó sử dụng luồng quy trình tương tự mà không có ngoại lệ.
Trái ngược với các lý thuyết thường được chấp nhận, try
/ catch
có thể có những tác động đáng kể về hiệu suất, và đó là liệu một ngoại lệ có được ném ra hay không!
Trước đây đã được bao phủ trong một vài bài đăng trên blog của Microsoft MVP trong những năm qua, và tôi tin tưởng bạn có thể tìm thấy chúng một cách dễ dàng chưa StackOverflow quan tâm rất nhiều về nội dung vì vậy tôi sẽ cung cấp các liên kết đến một số trong số họ là phụ bằng chứng:
try
/ catch
/finally
( và phần hai ), của Peter Ritchie khám phá các tính năng tối ưu màtry
/catch
/finally
vô hiệu hóa (và tôi sẽ đi sâu hơn vào vấn đề này với các trích dẫn từ tiêu chuẩn)Parse
so với so TryParse
vớiConvertTo
của Ian Huff tuyên bố trắng trợn rằng "việc xử lý ngoại lệ rất chậm" và thể hiện điểm này bằng cách phân loạiInt.Parse
vàInt.TryParse
chống lại nhau ... Đối với bất kỳ ai khăng khăng rằngTryParse
sử dụngtry
/catch
hậu trường, điều này nên làm sáng tỏ!Cũng có câu trả lời này cho thấy sự khác biệt giữa mã được tháo rời có- và không sử dụng try
/ catch
.
Có vẻ như quá rõ ràng rằng có là một chi phí mà là trắng trợn thể quan sát được trong việc tạo mã, và chi phí mà ngay cả dường như được công nhận bởi những người Microsoft giá trị! Tuy nhiên, tôi đang lặp lại internet ...
Có, có hàng tá hướng dẫn MSIL bổ sung cho một dòng mã tầm thường và điều đó thậm chí không bao gồm các tối ưu bị vô hiệu hóa nên về mặt kỹ thuật, đó là một tối ưu hóa vi mô.
Tôi đã đăng một câu trả lời cách đây nhiều năm đã bị xóa vì nó tập trung vào năng suất của các lập trình viên (tối ưu hóa vĩ mô).
Điều này thật đáng tiếc vì không tiết kiệm được vài nano giây ở đây và ở đó thời gian CPU có thể bù đắp cho nhiều giờ tối ưu hóa thủ công tích lũy của con người. Sếp của bạn trả nhiều tiền hơn cho cái nào: một giờ dành cho bạn hay một giờ với máy tính đang chạy? Tại thời điểm nào chúng ta rút phích cắm và thừa nhận rằng đã đến lúc mua một chiếc máy tính nhanh hơn ?
Rõ ràng, chúng ta nên tối ưu hóa các ưu tiên của mình , không chỉ mã của chúng ta! Trong câu trả lời cuối cùng của tôi, tôi đã rút ra sự khác biệt giữa hai đoạn mã.
Sử dụng try
/ catch
:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Không sử dụng try
/ catch
:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Hãy xem xét từ quan điểm của một nhà phát triển bảo trì, điều này có nhiều khả năng làm lãng phí thời gian của bạn hơn, nếu không phải trong việc lập hồ sơ / tối ưu hóa (đã đề cập ở trên) mà thậm chí sẽ không cần thiết nếu nó không phải là vấn đề try
/ catch
, sau đó cuộn qua mã nguồn ... Một trong số đó có thêm bốn dòng rác soạn sẵn!
Khi ngày càng có nhiều trường được đưa vào một lớp, tất cả rác của bản soạn sẵn này sẽ tích tụ (cả trong mã nguồn và mã được tháo rời) vượt quá mức hợp lý. Bốn dòng thừa trên mỗi trường, và chúng luôn luôn giống nhau ... Chúng ta không được dạy để tránh lặp lại chính mình sao? Tôi cho rằng chúng ta có thể ẩn try
/ catch
đằng sau một số trừu tượng tự pha chế, nhưng ... sau đó chúng ta cũng có thể tránh các ngoại lệ (tức là sử dụngInt.TryParse
).
Đây thậm chí không phải là một ví dụ phức tạp; Tôi đã thấy những nỗ lực khởi tạo các lớp mới trong try
/ catch
. Hãy xem xét rằng tất cả mã bên trong của hàm tạo sau đó có thể bị loại khỏi một số tối ưu nhất định mà nếu không trình biên dịch sẽ tự động áp dụng. Còn cách nào tốt hơn để đưa ra giả thuyết rằng trình biên dịch chậm , trái ngược với việc trình biên dịch đang làm chính xác những gì nó được yêu cầu ?
Giả sử một ngoại lệ được tạo bởi nhà xây dựng nói trên và một số lỗi được kích hoạt do đó, nhà phát triển bảo trì kém sau đó phải theo dõi nó. Đó có thể không phải là một nhiệm vụ dễ dàng như vậy, vì không giống như mã spaghetti của cơn ác mộng goto , try
/ catch
có thể gây ra sự lộn xộn trong ba chiều , vì nó có thể di chuyển ngăn xếp thành không chỉ các phần khác của cùng một phương thức mà còn cả các lớp và phương thức khác , tất cả sẽ được quan sát bởi nhà phát triển bảo trì, một cách khó khăn ! Tuy nhiên, chúng tôi được nói rằng "goto là nguy hiểm", heh!
Ở phần cuối, tôi đề cập, try
/ catch
có lợi ích của nó, đó là, nó được thiết kế để vô hiệu hóa tính năng tối ưu hóa ! Nếu bạn muốn, nó là một trợ giúp gỡ lỗi ! Đó là những gì nó được thiết kế cho và đó là những gì nó nên được sử dụng như ...
Tôi đoán đó cũng là một điểm tích cực. Nó có thể được sử dụng để vô hiệu hóa các tối ưu hóa có thể làm tê liệt các thuật toán truyền thông điệp an toàn, lành mạnh cho các ứng dụng đa luồng và để nắm bắt các điều kiện cuộc đua có thể xảy ra;) Đó là về kịch bản duy nhất tôi có thể nghĩ đến để sử dụng try / catch. Thậm chí điều đó có các lựa chọn thay thế.
Có gì optimisations làm try
, catch
và finally
vô hiệu hóa?
AKA
Làm thế nào là try
, catch
và finally
hữu ích như gỡ lỗi trợ?
chúng là rào cản viết. Điều này xuất phát từ tiêu chuẩn:
12.3.3.13 Câu lệnh try-catch
Đối với một câu lệnh stmt có dạng:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n
- Trạng thái gán xác định của v ở đầu khối try cũng giống như trạng thái gán xác định của v ở đầu stmt .
- Trạng thái gán xác định của v ở đầu catch-block-i (với i bất kỳ ) cũng giống như trạng thái gán xác định của v ở đầu stmt .
- Trạng thái gán xác định của v tại điểm cuối của stmt chắc chắn được gán nếu (và chỉ khi) v chắc chắn được gán tại điểm cuối của khối try và mọi khối bắt-i (với mọi i từ 1 đến n ).
Nói cách khác, ở đầu mỗi try
câu lệnh:
try
câu lệnh phải hoàn thành, điều này yêu cầu khóa luồng để bắt đầu, điều này rất hữu ích cho việc gỡ lỗi các điều kiện cuộc đua!try
câu lệnhMột câu chuyện tương tự xảy ra cho mỗi catch
tuyên bố; giả sử trong try
câu lệnh của bạn (hoặc một phương thức khởi tạo hoặc hàm mà nó gọi ra, v.v.) bạn gán cho biến vô nghĩa đó (giả sử, garbage=42;
), trình biên dịch không thể loại bỏ câu lệnh đó, bất kể nó không liên quan đến hành vi quan sát được của chương trình. . Nhiệm vụ cần phải hoàn thành trước khi catch
khối được nhập.
Đối với những gì nó đáng giá, hãy finally
kể một câu chuyện suy thoái tương tự :
12.3.3.14 Câu lệnh thử cuối cùng
Đối với một câu lệnh thử stmt có dạng:
try try-block finally finally-block
• Trạng thái gán xác định của v ở đầu khối try cũng giống như trạng thái gán xác định của v ở đầu stmt .
• Trạng thái gán xác định của v ở đầu khối cuối cùng cũng giống như trạng thái gán xác định của v ở đầu stmt .
• Trạng thái gán xác định của v tại điểm cuối của stmt chắc chắn được gán nếu (và chỉ khi) một trong hai: o v chắc chắn được gán ở điểm cuối của khối thử o vchắc chắn được chỉ định ở điểm cuối của khối cuối cùng Nếu việc chuyển luồng điều khiển (chẳng hạn như câu lệnh goto ) được thực hiện bắt đầu bên trong khối try và kết thúc bên ngoài khối thử , thì v cũng được coi là chắc chắn được chỉ định trên đó điều khiển chuyển luồng nếu v chắc chắn được gán tại điểm cuối của khối cuối cùng . (Đây không phải là chỉ nếu — nếu v chắc chắn được chỉ định vì một lý do khác trong quá trình chuyển luồng điều khiển này, thì nó vẫn được coi là chắc chắn được gán.)
12.3.3.15 Câu lệnh try-catch-last
Phân tích nhiệm vụ xác định cho một câu lệnh try - catch - last của biểu mẫu:
try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n finally finally-block
được thực hiện như thể tuyên bố là một thử - cuối cùng đã tuyên bố kèm theo một thử - bắt tuyên bố:
try { try try-block catch ( ... ) catch-block-1 ... catch ( ... ) catch-block-n } finally finally-block
Chưa kể nếu nó nằm trong một phương thức thường được gọi, nó có thể ảnh hưởng đến hành vi tổng thể của ứng dụng.
Ví dụ, tôi coi việc sử dụng Int32.Parse là một phương pháp không tốt trong hầu hết các trường hợp vì nó ném ra các ngoại lệ cho một số thứ có thể dễ dàng bị bắt.
Vì vậy, để kết thúc mọi thứ được viết ở đây:
1) Sử dụng khối try..catch để bắt lỗi không mong muốn - hầu như không có hình phạt hiệu suất.
2) Không sử dụng ngoại lệ cho các lỗi ngoại lệ nếu bạn có thể tránh được.
Tôi đã viết một bài báo về điều này một thời gian trước vì có rất nhiều người hỏi về điều này vào thời điểm đó. Bạn có thể tìm thấy nó và mã kiểm tra tại http://www.blackwasp.co.uk/SpeedTestTryCatch.aspx .
Kết quả là có một lượng nhỏ chi phí cho một khối thử / bắt nhưng quá nhỏ nên nó nên được bỏ qua. Tuy nhiên, nếu bạn đang chạy khối try / catch trong các vòng lặp được thực thi hàng triệu lần, bạn có thể cân nhắc việc di chuyển khối ra bên ngoài vòng lặp nếu có thể.
Vấn đề hiệu suất quan trọng với các khối try / catch là khi bạn thực sự bắt được một ngoại lệ. Điều này có thể gây ra sự chậm trễ đáng kể cho ứng dụng của bạn. Tất nhiên, khi mọi thứ diễn ra không như ý muốn, hầu hết các nhà phát triển (và rất nhiều người dùng) nhận ra việc tạm dừng là một ngoại lệ sắp xảy ra! Chìa khóa ở đây là không sử dụng xử lý ngoại lệ cho các hoạt động bình thường. Như tên cho thấy, chúng rất đặc biệt và bạn nên làm mọi thứ có thể để tránh chúng bị ném. Bạn không nên sử dụng chúng như một phần của luồng dự kiến của một chương trình đang hoạt động chính xác.
Tôi đã thực hiện một mục blog về chủ đề này vào năm ngoái. Kiểm tra nó ra. Điểm mấu chốt là hầu như không có chi phí cho khối thử nếu không có ngoại lệ nào xảy ra - và trên máy tính xách tay của tôi, một ngoại lệ là khoảng 36μs. Điều đó có thể ít hơn bạn mong đợi, nhưng hãy nhớ rằng những kết quả đó ở mức thấp. Ngoài ra, các ngoại lệ đầu tiên thực sự chậm.
try
/ catch
quá nhiều không? Heh heh), nhưng có vẻ như bạn đang tranh cãi với thông số ngôn ngữ và một số MS MVP cũng đã viết blog về chủ đề này, cung cấp các phép đo trái với lời khuyên của bạn ... Tôi cởi mở với gợi ý rằng nghiên cứu tôi đã thực hiện là sai, nhưng tôi cần đọc mục blog của bạn để xem nó nói gì.
try-catch
khối so với tryparse()
các phương thức, nhưng khái niệm thì giống nhau.
Việc viết, gỡ lỗi và duy trì mã dễ dàng hơn nhiều mà không có thông báo lỗi trình biên dịch, thông báo cảnh báo phân tích mã và các ngoại lệ được chấp nhận thông thường (đặc biệt là các ngoại lệ được ném ở một nơi và được chấp nhận ở nơi khác). Bởi vì nó dễ dàng hơn, mã trung bình sẽ được viết tốt hơn và ít lỗi hơn.
Đối với tôi, lập trình viên và chi phí chất lượng là lý lẽ chính để chống lại việc sử dụng try-catch cho quy trình.
Chi phí máy tính của các trường hợp ngoại lệ là không đáng kể khi so sánh và thường là rất nhỏ về khả năng của ứng dụng để đáp ứng các yêu cầu hiệu suất trong thế giới thực.
Tôi thực sự thích bài đăng trên blog của Hafthor , và để thêm hai xu của tôi vào cuộc thảo luận này, tôi muốn nói rằng, tôi luôn dễ dàng để DỮ LIỆU chỉ ném một loại ngoại lệ (DataAccessException). Bằng cách này, NHÀ KINH DOANH của tôi biết ngoại lệ nào sẽ xảy ra và nắm bắt nó. Sau đó, tùy thuộc vào các quy tắc nghiệp vụ khác (ví dụ: nếu đối tượng kinh doanh của tôi tham gia vào quy trình làm việc, v.v.), tôi có thể đưa ra một ngoại lệ mới (BusinessObjectException) hoặc tiếp tục mà không cần / ném lại.
Tôi muốn nói rằng đừng ngần ngại sử dụng try..catch bất cứ khi nào cần thiết và sử dụng nó một cách khôn ngoan!
Ví dụ: phương pháp này tham gia vào một quy trình làm việc ...
Bình luận?
public bool DeleteGallery(int id)
{
try
{
using (var transaction = new DbTransactionManager())
{
try
{
transaction.BeginTransaction();
_galleryRepository.DeleteGallery(id, transaction);
_galleryRepository.DeletePictures(id, transaction);
FileManager.DeleteAll(id);
transaction.Commit();
}
catch (DataAccessException ex)
{
Logger.Log(ex);
transaction.Rollback();
throw new BusinessObjectException("Cannot delete gallery. Ensure business rules and try again.", ex);
}
}
}
catch (DbTransactionException ex)
{
Logger.Log(ex);
throw new BusinessObjectException("Cannot delete gallery.", ex);
}
return true;
}
Chúng ta có thể đọc trong Ngôn ngữ lập trình Ngữ dụng của Michael L. Scott rằng các trình biên dịch ngày nay không thêm bất kỳ chi phí nào trong trường hợp phổ biến, điều này có nghĩa là, khi không có ngoại lệ nào xảy ra. Vì vậy, mọi tác phẩm được thực hiện trong thời gian biên dịch. Nhưng khi một ngoại lệ được đưa ra trong thời gian chạy, trình biên dịch cần thực hiện tìm kiếm nhị phân để tìm ngoại lệ chính xác và điều này sẽ xảy ra cho mọi lần ném mới mà bạn đã thực hiện.
Nhưng các trường hợp ngoại lệ là ngoại lệ và chi phí này hoàn toàn có thể chấp nhận được. Nếu bạn cố gắng thực hiện Xử lý ngoại lệ mà không có ngoại lệ và thay vào đó sử dụng mã lỗi trả về, có thể bạn sẽ cần câu lệnh if cho mọi chương trình con và điều này sẽ phát sinh chi phí thực sự theo thời gian thực. Bạn biết câu lệnh if được chuyển đổi thành một vài lệnh hợp ngữ, lệnh này sẽ được thực hiện mỗi khi bạn nhập vào các quy trình con của mình.
Xin lỗi về tiếng Anh của tôi, hy vọng rằng nó sẽ giúp bạn. Thông tin này dựa trên sách đã trích dẫn, để biết thêm thông tin, hãy tham khảo Chương 8.5 Xử lý ngoại lệ.
Hãy để chúng tôi phân tích một trong những chi phí lớn nhất có thể có của khối thử / bắt khi được sử dụng ở những nơi không cần sử dụng:
int x;
try {
x = int.Parse("1234");
}
catch {
return;
}
// some more code here...
Và đây là cái mà không cần thử / bắt:
int x;
if (int.TryParse("1234", out x) == false) {
return;
}
// some more code here
Không tính khoảng trắng không đáng kể, người ta có thể nhận thấy rằng hai đoạn mã tương đương này gần như chính xác cùng độ dài tính bằng byte. Cái sau chứa ít hơn 4 byte thụt lề. Đó có phải là một điều xấu?
Để thêm sự xúc phạm đến thương tích, một sinh viên quyết định lặp lại trong khi đầu vào có thể được phân tích cú pháp dưới dạng int. Giải pháp mà không cần try / catch có thể là:
while (int.TryParse(...))
{
...
}
Nhưng điều này trông như thế nào khi sử dụng try / catch?
try {
for (;;)
{
x = int.Parse(...);
...
}
}
catch
{
...
}
Thử / bắt khối là cách kỳ diệu để lãng phí thụt lề và chúng tôi thậm chí vẫn không biết lý do nó không thành công! Hãy tưởng tượng người thực hiện gỡ lỗi cảm thấy như thế nào, khi mã tiếp tục thực thi qua một lỗ hổng logic nghiêm trọng, thay vì dừng lại với một lỗi ngoại lệ rõ ràng. Các khối try / catch là xác thực / vệ sinh dữ liệu của một kẻ lười biếng.
Một trong những chi phí nhỏ hơn là khối try / catch thực sự vô hiệu hóa một số tối ưu hóa nhất định: http://msmvps.com/blogs/peterritchie/archive/2007/06/22/performance-implication-of-try-catch-finally.aspx . Tôi đoán đó cũng là một điểm tích cực. Nó có thể được sử dụng để vô hiệu hóa các tối ưu hóa có thể làm tê liệt các thuật toán truyền thông báo an toàn, lành mạnh cho các ứng dụng đa luồng và để nắm bắt các điều kiện cuộc đua có thể xảy ra;) Đó là về kịch bản duy nhất tôi có thể nghĩ đến để sử dụng try / catch. Thậm chí điều đó có các lựa chọn thay thế.
Int.Parse
lợi Int.TryParse
.