Hiệu suất khác nhau giữa các bản dựng gỡ lỗi và phát hành


280

Tôi phải thừa nhận rằng, thông thường tôi không bận tâm đến việc chuyển đổi giữa các cấu hình Gỡ lỗiPhát hành trong chương trình của mình và tôi thường chọn sử dụng cấu hình Gỡ lỗi , ngay cả khi các chương trình được triển khai thực sự tại khách hàng.

Theo tôi biết, sự khác biệt duy nhất giữa các cấu hình này nếu bạn không thay đổi thủ công là DebugDEBUGhằng số được xác định và Bản phát hànhmã Tối ưu hóa được kiểm tra.

Vì vậy, câu hỏi của tôi thực sự có hai mặt:

  1. Có nhiều sự khác biệt hiệu suất giữa hai cấu hình này. Có bất kỳ loại mã cụ thể nào sẽ gây ra sự khác biệt lớn trong hiệu suất ở đây, hoặc nó thực sự không quan trọng?

  2. Có loại mã nào sẽ chạy tốt trong cấu hình Debug có thể bị lỗi trong cấu hình Phát hành không , hoặc bạn có thể chắc chắn rằng mã được kiểm tra và hoạt động tốt trong cấu hình Debug cũng sẽ hoạt động tốt trong cấu hình Phát hành.


Câu trả lời:


511

Trình biên dịch C # không làm thay đổi IL phát ra rất nhiều trong bản dựng Phát hành. Đáng chú ý là nó không còn phát ra các opp NOP cho phép bạn đặt một điểm dừng trên một dấu ngoặc nhọn. Cái lớn nhất là trình tối ưu hóa được tích hợp trong trình biên dịch JIT. Tôi biết nó thực hiện các tối ưu hóa sau:

  • Phương pháp nội tuyến. Một cuộc gọi phương thức được thay thế bằng cách tiêm mã của phương thức. Đây là một cái lớn, nó làm cho người truy cập tài sản về cơ bản là miễn phí.

  • Phân bổ thanh ghi CPU. Các biến cục bộ và đối số phương thức có thể được lưu trữ trong một thanh ghi CPU mà không bao giờ (hoặc ít thường xuyên hơn) được lưu trữ trở lại khung ngăn xếp. Đây là một vấn đề lớn, đáng chú ý vì làm cho việc gỡ lỗi mã được tối ưu hóa trở nên khó khăn. Và cho từ khóa dễ bay hơi một ý nghĩa.

  • Kiểm tra chỉ số mảng loại bỏ. Một tối ưu hóa quan trọng khi làm việc với các mảng (tất cả các lớp bộ sưu tập .NET sử dụng một mảng bên trong). Khi trình biên dịch JIT có thể xác minh rằng một vòng lặp không bao giờ lập chỉ mục một mảng ngoài giới hạn thì nó sẽ loại bỏ kiểm tra chỉ mục. Cái lớn.

  • Vòng lặp không kiểm soát. Vòng lặp với cơ thể nhỏ được cải thiện bằng cách lặp lại mã lên đến 4 lần trong cơ thể và lặp ít hơn. Giảm chi phí chi nhánh và cải thiện các tùy chọn thực thi siêu vô hướng của bộ xử lý.

  • Loại bỏ mã chết. Một câu lệnh như if (false) {/ ... /} bị loại bỏ hoàn toàn. Điều này có thể xảy ra do liên tục gấp và nội tuyến. Các trường hợp khác là nơi trình biên dịch JIT có thể xác định rằng mã không có tác dụng phụ có thể xảy ra. Tối ưu hóa này là những gì làm cho mã hồ sơ rất khó khăn.

  • Mã nâng hàng. Mã bên trong một vòng lặp không bị ảnh hưởng bởi vòng lặp có thể được chuyển ra khỏi vòng lặp. Trình tối ưu hóa của trình biên dịch C sẽ dành nhiều thời gian hơn cho việc tìm kiếm cơ hội để nâng hàng. Tuy nhiên, đó là một tối ưu hóa đắt tiền do phân tích luồng dữ liệu cần thiết và jitter không thể dành thời gian nên chỉ có các trường hợp rõ ràng. Buộc các lập trình viên .NET phải viết mã nguồn tốt hơn và tự nâng lên.

  • Loại bỏ biểu thức phụ phổ biến. x = y + 4; z = y + 4; trở thành z = x; Khá phổ biến trong các câu lệnh như Dest [ix + 1] = src [ix + 1]; được viết để dễ đọc mà không giới thiệu một biến trợ giúp. Không cần phải thỏa hiệp khả năng đọc.

  • Gấp liên tục. x = 1 + 2; trở thành x = 3; Ví dụ đơn giản này được trình biên dịch bắt gặp sớm, nhưng xảy ra vào thời điểm JIT khi các tối ưu hóa khác làm cho điều này có thể.

  • Sao chép tuyên truyền. x = a; y = x; trở thành y = a; Điều này giúp người cấp phát đăng ký đưa ra quyết định tốt hơn. Nó là một vấn đề lớn trong jitter x86 vì nó có ít thanh ghi để làm việc. Có nó chọn đúng là rất quan trọng để hoàn thiện.

Đây là những tối ưu hóa rất quan trọng có thể tạo ra sự khác biệt lớn khi, ví dụ, bạn cấu hình bản dựng Debug của ứng dụng của bạn và so sánh nó với bản dựng Phát hành. Điều đó chỉ thực sự quan trọng mặc dù khi mã nằm trên đường dẫn quan trọng của bạn, 5 đến 10% mã bạn viết thực sự ảnh hưởng đến sự hoàn hảo của chương trình của bạn. Trình tối ưu hóa JIT không đủ thông minh để biết trước điều gì là quan trọng, nó chỉ có thể áp dụng quay số "biến nó thành mười một" cho tất cả các mã.

Kết quả hiệu quả của những tối ưu hóa này trong thời gian thực hiện chương trình của bạn thường bị ảnh hưởng bởi mã chạy ở nơi khác. Đọc tệp, thực hiện truy vấn dbase, v.v. Làm cho công việc trình tối ưu hóa JIT hoàn toàn vô hình. Nó không phiền đâu :)

Trình tối ưu hóa JIT là mã khá đáng tin cậy, chủ yếu là do nó đã được đưa vào thử nghiệm hàng triệu lần. Rất hiếm khi gặp sự cố trong phiên bản xây dựng Phiên bản của chương trình của bạn. Nó không xảy ra tuy nhiên. Cả jitter x64 và x86 đều gặp vấn đề với cấu trúc. Jitter x86 gặp rắc rối với tính nhất quán của dấu phẩy động, tạo ra các kết quả khác nhau một cách tinh tế khi các trung gian của phép tính dấu phẩy động được giữ trong một thanh ghi FPU với độ chính xác 80 bit thay vì bị cắt bớt khi bị xóa vào bộ nhớ.


23
Tôi không nghĩ tất cả các bộ sưu tập đều sử dụng (các) mảng: LinkedList<T>không, mặc dù nó không được sử dụng thường xuyên.
Svick

Tôi nghĩ rằng CLR định cấu hình độ chính xác của FPU thành 53 bit (khớp với độ rộng gấp đôi 64 bit), do đó, không nên có phép tính kép mở rộng 80 bit cho các giá trị Float64. Tuy nhiên, tính toán Float32 có thể được tính toán với độ chính xác 53 bit này và chỉ bị cắt ngắn khi được lưu vào bộ nhớ.
Govert

2
Các volatiletừ khóa không áp dụng cho các biến cục bộ được lưu trữ trong một stack frame. Từ tài liệu tại msdn.microsoft.com/en-us/l Library / x13ttww7.aspx : "Từ khóa dễ bay hơi chỉ có thể được áp dụng cho các trường của một lớp hoặc cấu trúc. Các biến cục bộ không thể được khai báo là biến động."
Kris Vandermotten

8
như một sửa đổi khiêm tốn, tôi đoán điều thực sự tạo ra sự khác biệt giữa Debugvà các Releasebản dựng trong vấn đề này là hộp kiểm "tối ưu hóa mã" thường được bật Releasenhưng tắt cho Debug. Chỉ để đảm bảo độc giả không bắt đầu nghĩ rằng có "ma thuật", sự khác biệt vô hình giữa hai cấu hình xây dựng vượt xa những gì được tìm thấy trên trang thuộc tính dự án trong Visual Studio.
chiccodoro

3
Có lẽ đáng nói là hầu như không có phương thức nào trên System.Diagnostics.Debug làm bất cứ điều gì trong bản dựng gỡ lỗi. Ngoài ra các biến không được hoàn thiện khá nhanh chóng ( stackoverflow.com/a/7165380/20553 ).
Martin Brown

23
  1. Vâng, có nhiều sự khác biệt về hiệu suất và những điều này thực sự áp dụng trên toàn bộ mã của bạn. Gỡ lỗi thực hiện rất ít tối ưu hóa hiệu suất và chế độ phát hành rất nhiều;

  2. Chỉ mã dựa trên DEBUGhằng số có thể thực hiện khác với bản dựng phát hành. Bên cạnh đó, bạn không nên nhìn thấy bất kỳ vấn đề.

Một ví dụ về mã khung phụ thuộc vào DEBUGhằng số là Debug.Assert()phương thức, có thuộc tính [Conditional("DEBUG)"]được xác định. Điều này có nghĩa là nó cũng phụ thuộc vào DEBUGhằng số và điều này không được bao gồm trong bản phát hành.


2
Điều này hoàn toàn đúng, nhưng bạn có thể đo được sự khác biệt không? Hoặc nhận thấy một sự khác biệt trong khi sử dụng một chương trình? Tất nhiên tôi không muốn khuyến khích bất cứ ai phát hành phần mềm của họ ở chế độ gỡ lỗi, nhưng câu hỏi là liệu có sự khác biệt lớn về hiệu suất và tôi không thể thấy điều đó.
testalino

2
Cũng đáng chú ý là các phiên bản gỡ lỗi tương quan với mã nguồn gốc ở mức độ cao hơn nhiều so với các phiên bản phát hành. Nếu bạn nghĩ (tuy nhiên không chắc) rằng ai đó có thể cố gắng thiết kế ngược lại các tệp thực thi của bạn, bạn không muốn làm cho chúng dễ dàng hơn bằng cách triển khai các phiên bản gỡ lỗi.
jwheron

2
@testalino - Chà, ngày nay thật khó khăn. Bộ xử lý đã nhận được nhanh đến mức người dùng hầu như không chờ đợi một quá trình thực sự thực thi mã vì một hành động của người dùng, vì vậy đây chỉ là tương đối. Tuy nhiên, nếu bạn thực sự đang thực hiện một quá trình dài, có, bạn sẽ nhận thấy. Đoạn mã sau ví dụ chạy chậm hơn 40% trong DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Pieter van Ginkel

2
Ngoài ra, nếu bạn đang bật asp.netvà sử dụng gỡ lỗi thay vì phát hành, một số tập lệnh có thể được thêm vào trang của bạn, chẳng hạn như: MicrosoftAjax.debug.jscó khoảng 7k dòng.
BrunoLM

13

Điều này phụ thuộc rất nhiều vào bản chất của ứng dụng của bạn. Nếu ứng dụng của bạn nặng UI, có lẽ bạn sẽ không nhận thấy bất kỳ sự khác biệt nào vì thành phần chậm nhất được kết nối với máy tính hiện đại là người dùng. Nếu bạn sử dụng một số hình ảnh động UI, bạn có thể muốn kiểm tra xem bạn có thể nhận thấy bất kỳ độ trễ đáng chú ý nào khi chạy trong bản dựng DEBUG không.

Tuy nhiên, nếu bạn có nhiều phép tính nặng tính toán, thì bạn sẽ nhận thấy sự khác biệt (có thể cao tới 40% như @Pieter đã đề cập, mặc dù điều này phụ thuộc vào bản chất của phép tính).

Về cơ bản, đó là một sự đánh đổi trong thiết kế. Nếu bạn đang phát hành theo bản dựng DEBUG, thì nếu người dùng gặp sự cố, bạn có thể nhận được một dấu vết có ý nghĩa hơn và bạn có thể chẩn đoán linh hoạt hơn nhiều. Bằng cách phát hành trong bản dựng DEBUG, bạn cũng tránh được trình tối ưu hóa tạo ra Heisenbugs tối nghĩa .


11
  • Kinh nghiệm của tôi là các ứng dụng cỡ trung bình hoặc lớn hơn phản ứng nhanh hơn đáng kể trong bản dựng Phát hành. Hãy dùng thử với ứng dụng của bạn và xem cảm giác như thế nào.

  • Một điều có thể cắn bạn với các bản dựng Phát hành là mã xây dựng Debug đôi khi có thể triệt tiêu các điều kiện chủng tộc và các lỗi liên quan đến luồng khác. Mã được tối ưu hóa có thể dẫn đến sắp xếp lại lệnh và thực thi nhanh hơn có thể làm trầm trọng thêm các điều kiện cuộc đua nhất định.


9

Bạn không bao giờ nên phát hành bản dựng .NET Debug vào sản xuất. Nó có thể chứa mã xấu để hỗ trợ Chỉnh sửa và Tiếp tục hoặc ai biết những gì khác. Theo tôi biết, điều này chỉ xảy ra trong VB chứ không phải C # (lưu ý: bài đăng gốc được gắn thẻ C #) , nhưng nó vẫn sẽ đưa ra lý do để tạm dừng như những gì Microsoft nghĩ rằng họ được phép làm với bản dựng Debug. Trong thực tế, trước .NET 4.0, mã VB rò rỉ bộ nhớ tỷ lệ thuận với số lượng phiên bản của các đối tượng có các sự kiện mà bạn xây dựng để hỗ trợ Chỉnh sửa và Tiếp tục. (Mặc dù điều này được báo cáo là cố định cho mỗi https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , các mã được tạo trông khó chịu, tạo WeakReferencecác đối tượng và thêm chúng vào một danh sách tĩnh trong khigiữ một khóa ) Tôi chắc chắn không muốn bất kỳ loại hỗ trợ gỡ lỗi nào trong môi trường sản xuất!


Tôi đã phát hành bản dựng Debug nhiều lần và chưa bao giờ thấy sự cố. Sự khác biệt duy nhất có lẽ là ứng dụng phía máy chủ của chúng tôi không phải là ứng dụng web hỗ trợ nhiều người dùng. Nhưng nó là một ứng dụng phía máy chủ với tải xử lý rất cao. Từ kinh nghiệm của tôi, sự khác biệt giữa Debug và Release dường như hoàn toàn trên lý thuyết. Tôi chưa bao giờ thấy bất kỳ sự khác biệt thực tế với bất kỳ ứng dụng của chúng tôi.
Sam Goldberg

5

Theo kinh nghiệm của tôi, điều tồi tệ nhất xuất hiện từ chế độ Phát hành là "lỗi phát hành" tối nghĩa. Do IL (ngôn ngữ trung gian) được tối ưu hóa trong chế độ Phát hành, nên có khả năng xảy ra lỗi không xuất hiện trong chế độ Gỡ lỗi. Có các câu hỏi SO khác về vấn đề này: Lý do phổ biến cho các lỗi trong phiên bản phát hành không có trong chế độ gỡ lỗi

Điều này đã xảy ra với tôi một hoặc hai lần khi một ứng dụng bảng điều khiển đơn giản sẽ chạy hoàn toàn tốt trong chế độ Gỡ lỗi, nhưng với cùng một đầu vào, sẽ bị lỗi trong chế độ Phát hành. Những lỗi này rất khó gỡ lỗi (theo định nghĩa của chế độ Phát hành, trớ trêu thay).


Để theo dõi, đây là một bài viết đưa ra ví dụ về Lỗi phát hành: codeproject.com/KB/trace/ReleaseBug.aspx
Roly

Đây vẫn là một vấn đề nếu ứng dụng được kiểm tra và phê duyệt với cài đặt Gỡ lỗi, ngay cả khi ứng dụng đó khắc phục lỗi, nếu điều đó khiến việc xây dựng bản phát hành bị lỗi trong quá trình triển khai.
yvind Bråthen

4

Tôi muốn nói rằng 1) phần lớn phụ thuộc vào việc thực hiện của bạn. Thông thường, sự khác biệt không quá lớn. Tôi đã thực hiện nhiều phép đo và thường tôi không thể thấy sự khác biệt. Nếu bạn sử dụng mã không được quản lý, rất nhiều mảng lớn và những thứ tương tự, sự khác biệt hiệu năng sẽ lớn hơn một chút, nhưng không phải là một thế giới khác (như trong C ++). 2) Thông thường trong mã phát hành, ít lỗi hơn được hiển thị (dung sai cao hơn), do đó một công tắc sẽ hoạt động tốt.


1
Đối với mã bị ràng buộc IO, bản dựng phát hành có thể dễ dàng gỡ lỗi nhanh hơn.
Richard

0
    **Debug Mode:**
    Developer use debug mode for debugging the web application on live/local server. Debug mode allow developers to break the execution of program using interrupt 3 and step through the code. Debug mode has below features:
   1) Less optimized code
   2) Some additional instructions are added to enable the developer to set a breakpoint on every source code line.
   3) More memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are not cached.
   5) It has big size, and runs slower.

    **Release Mode:**
    Developer use release mode for final deployment of source code on live server. Release mode dlls contain optimized code and it is for customers. Release mode has below features:
   1) More optimized code
   2) Some additional instructions are removed and developer cant set a breakpoint on every source code line.
   3) Less memory is used by the source code at runtime.
   4) Scripts & images downloaded by webresource.axd are cached.
   5) It has small size, and runs fast.

2
có vẻ như trong chế độ phát hành đôi khi các yếu tố đầu tiên của danh sách không được đánh số chính xác. Ngoài ra một số yếu tố trong danh sách được nhân đôi. :)
Gian Paolo
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.