Різниця в продуктивності між налагодженнями та версіями версій


280

Я мушу визнати, що зазвичай я не переймаюся перемиканням між конфігураціями налагодження та випуску в своїй програмі, і я зазвичай вирішив перейти до конфігурації налагодження , навіть коли програми фактично розгорнуті в місці клієнтів.

Наскільки я знаю, єдина відмінність цих конфігурацій, якщо ви не змінюєте її вручну, полягає в тому, що для налагодження встановлена DEBUGконстанта, а в Release встановлено перевірку коду оптимізації .

Тож мої запитання насправді подвійні:

  1. Чи є великі відмінності в роботі між цими двома конфігураціями. Чи є якийсь конкретний тип коду, який спричинить великі відмінності у продуктивності, чи він насправді не такий важливий?

  2. Чи є який-небудь тип коду, який буде нормально працювати в конфігурації налагодження, який може не працювати під час конфігурації випуску , або ви можете бути впевнені, що код, який тестується і працює нормально в конфігурації налагодження , також буде добре працювати в конфігурації випуску.


Відповіді:


511

Сам компілятор C # не сильно змінює випромінюваний IL в складанні Release. Примітно те, що він більше не випромінює NOP-коди, які дозволяють встановити точку розриву на фігурній дужці. Великий - це оптимізатор, вбудований у компілятор JIT. Я знаю, що це робить такі оптимізації:

  • Метод вкладки. Виклик методу замінюється введенням коду методу. Цей великий, він робить доступ до власності по суті безкоштовним.

  • Розподіл реєстру процесора. Локальні змінні та аргументи методу можуть залишатися збереженими в регістрі процесора, не зберігаючи ніколи (або рідше) назад у кадр стека. Це великий варіант, який відрізняється тим, що ускладнює оптимізований код налагодження таким чином складним. І даючи Летючі ключове слово значення.

  • Усунення індексу перевірки масиву. Важлива оптимізація під час роботи з масивами (усі класи .NET колекція використовують масив всередині). Коли компілятор JIT зможе перевірити, що цикл ніколи не індексує масив поза межами діапазону, він усуне перевірку індексу. Великий.

  • Розгортання циклу. Петлі з маленькими тілами поліпшуються шляхом повторення коду до 4 разів у тілі та меншої петлі. Зменшує вартість філій та покращує надскарміальні варіанти виконання процесора.

  • Усунення мертвого коду. Заява на зразок if (false) {/ ... /} видаляється повністю. Це може статися через постійне складання та накладки. Інші випадки, коли компілятор JIT може визначити, що код не має можливих побічних ефектів. Ця оптимізація - це те, що робить код профілювання таким складним.

  • Підйомник коду. Код всередині циклу, на який не впливає цикл, може бути переміщений з циклу. Оптимізатор компілятора С витратить набагато більше часу на пошук можливостей підняття. Однак, це дорога оптимізація завдяки необхідному аналізу потоку даних, і тремтіння не може дозволити собі час, тому є лише очевидними випадки. Примушують .NET програмістів писати кращий вихідний код і піднімати себе.

  • Поширене усунення суб-вирази. x = y + 4; z = y + 4; стає z = x; Досить поширений у таких висловлюваннях, як dest [ix + 1] = src [ix + 1]; написано для читабельності без введення допоміжної змінної. Не потрібно компрометувати читабельність.

  • Постійне складання. х = 1 + 2; стає x = 3; Цей простий приклад заздалегідь спіймається компілятором, але трапляється в JIT час, коли інші оптимізації роблять це можливим.

  • Копіювання розповсюдження х = а; у = х; стає y = a; Це допомагає розподільнику реєстру приймати кращі рішення. У джиттерах x86 це велика справа, оскільки з нею є декілька регістрів. Його вибір правильних важливих для перф.

Це дуже важливі оптимізації, які можуть істотно змінитись, коли, наприклад, ви профілюєте збірку налагодження свого додатка та порівняєте його зі збіркою Release. Це дійсно важливо, хоча, коли код знаходиться на вашому критичному шляху, від 5 до 10% написаного вами коду, що насправді впливає на перф. Вашої програми. Оптимізатор JIT недостатньо розумний, щоб наперед знати, що найважливіше, він може застосувати лише номер "повернути його до одинадцяти" для всього коду.

На ефективний результат цих оптимізацій часу виконання вашої програми часто впливає код, який працює в іншому місці. Читання файлів, виконання запиту на базу даних тощо. Робота оптимізатора JIT робить роботу непомітною. Але це не проти :)

Оптимізатор JIT є досить надійним кодом, головним чином тому, що він був випробуваний мільйони разів. Вкрай рідко виникають проблеми у версії збірки версій вашої програми. Однак це трапляється. Як у x64, так і у x86 тремтіння були проблеми з структурами. Джиттер x86 має проблеми з послідовністю з плаваючою точкою, створюючи незначно різні результати, коли проміжні інтервали обчислення з плаваючою точкою зберігаються в реєстрі FPU з 80-бітовою точністю, замість того, щоб укорочуватися під час передачі в пам'ять.


23
Я не думаю, що всі колекції використовують масив (и): LinkedList<T>ні, хоча він використовується не дуже часто.
svick

Я думаю, що CLR налаштовує FPU до 53-бітової точності (відповідність 64-бітових парних пар), тому не повинно бути 80-бітних розширених подвійних обчислень для значень Float64. Однак обчислення Float32 можуть бути обчислені з цією 53-бітовою точністю і усічені лише при збереженні в пам'яті.
Говер

2
volatileКлючове слово не відноситься до локальних змінних , що зберігаються в кадрі стека. З документації за адресою msdn.microsoft.com/en-us/library/x13ttww7.aspx : "Нестабільне ключове слово можна застосувати лише до полів класу або структури. Місцеві змінні не можна визнати мінливими."
Кріс Вандермоттен

8
як смиренна поправка, я думаю , що дійсно робить різницю між Debugі Releaseбудує в цьому відношенні є «оптимізує код» прапорець , який , як правило , протягом , Releaseале вирушили Debug. Просто переконайтеся, що читачі не починають думати, що існують «магічні», невидимі відмінності між двома конфігураціями побудови, які виходять за рамки того, що знаходиться на сторінці властивостей проекту у Visual Studio.
чікодоро

3
Можливо, варто відзначити, що практично жоден із методів System.Diagnostics.Debug не робить нічого в налагодженні. Також змінні не завершуються досить швидко (див. Stackoverflow.com/a/7165380/20553 ).
Мартін Браун

23
  1. Так, є багато відмінностей у продуктивності, і вони дійсно застосовуються у всьому коді. Налагодження робить дуже малу оптимізацію продуктивності та режим випуску дуже багато;

  2. Тільки код, який спирається на DEBUGконстанту, може працювати по-різному зі збіркою випусків. Крім того, ви не повинні бачити жодних проблем.

Прикладом рамкового коду, який залежить від DEBUGконстанти, є Debug.Assert()метод, [Conditional("DEBUG)"]визначений атрибут . Це означає, що це також залежить від DEBUGконстанти, і це не входить до складу версії.


2
Це все правда, але чи могли ви коли-небудь виміряти різницю? Або помітите різницю під час використання програми? Звичайно, я не хочу заохочувати кого-небудь випускати своє програмне забезпечення в режимі налагодження, але питання полягало в тому, чи існує велика різниця в продуктивності, і я цього не бачу.
testalino

2
Також варто відзначити, що версії налагодження співвідносяться з вихідним вихідним кодом набагато вищою мірою, ніж версії для випуску. Якщо ви вважаєте (наскільки це малоймовірно), що хтось може спробувати реінжиніринг ваших виконуваних файлів, ви не хочете полегшувати їх, розгортаючи налагоджувальні версії.
jwheron

2
@testalino - Ну, в ці дні це важко. Процесори настільки швидко дійшли, що користувач навряд чи чекає, коли процес дійсно виконає код через дії користувача, тому це все відносно. Однак якщо ви справді робите якийсь тривалий процес, так, ви помітите. Наступний код працює , наприклад , 40% повільніше при DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Пітер ван Гінкель

2
Крім того, якщо ви ввімкнули asp.netі використовуєте налагодження замість випуску, на вашій сторінці можуть бути додані деякі сценарії, такі як: MicrosoftAjax.debug.jsщо містить близько 7k рядків.
BrunoLM

13

Це сильно залежить від характеру вашої заявки. Якщо ваша програма важка для інтерфейсу, ви, мабуть, не помітите жодної різниці, оскільки саме повільний компонент, підключений до сучасного комп'ютера, - це користувач. Якщо ви використовуєте деякі анімації з інтерфейсу користувача, ви можете перевірити, чи можете ви помітити будь-яке помітне відставання під час запуску в DEBUG-збірці.

Однак якщо у вас багато обчислювальних обчислень, то ви помітите відмінності (це може бути до 40%, як згадував @Pieter, хоча це залежатиме від характеру розрахунків).

В основному це компромісний дизайн. Якщо ви випускаєте програму DEBUG build, тоді, якщо у користувачів виникнуть проблеми, ви можете отримати більш змістовне відстеження і ви можете зробити набагато більш гнучку діагностику. Випускаючи в DEBUG-збірці, ви також уникаєте оптимізатора, який створює незрозумілі Heisenbugs .


11
  • Мій досвід полягав у тому, що додатки середнього або більшого розміру помітніше реагують на збірку версій. Спробуйте спробувати свою програму і подивіться, як вона почувається.

  • Одне, що може вкусити вас за допомогою версій версій, - це те, що код збирання налагодження може іноді придушувати умови перегонів та інші помилки, пов’язані з потоком. Оптимізований код може призвести до переупорядкування інструкцій, а швидше виконання може погіршити певні умови перегонів.


9

Ніколи не слід випускати у виробництво вбудовану програму .NET Debug. Він може містити некрасивий код для підтримки редагування та продовження або хто знає що ще. Наскільки я знаю, це трапляється лише у VB, а не C # (зауважте: в оригінальній публікації позначено тег C #) , але це все ж повинно дати підстави для пауз щодо того, що Microsoft вважає, що їм дозволено робити зі збіркою налагодження. Насправді, перш ніж .NET 4.0, код VB просочує пам'ять, пропорційну кількості екземплярів об'єктів із подіями, які ви створюєте для підтримки редагування та продовження. (Хоча, як повідомляється, це виправлено за https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging , згенерований код виглядає неприємно, створюючи WeakReferenceоб'єкти і додаючи їх до статичного списку, одночаснотримаючи замок ) Я, звичайно, не хочу будь-якої подібної підтримки налагодження у виробничих умовах!


Я випускав Debug build багато разів, і ніколи не бачив проблеми. Єдина відмінність, можливо, полягає в тому, що наш серверний додаток - це не веб-додаток, який підтримує багато користувачів. Але це серверна програма з дуже високим навантаженням на обробку. З мого досвіду різниця між налагодженням та випуском здається абсолютно теоретичною. Я ніколи не бачив жодної практичної різниці з будь-яким із наших додатків.
Сем Голдберг

5

На мій досвід, найгірше, що вийшло з режиму випуску - це незрозумілі «помилки випуску». Оскільки IL (проміжна мова) оптимізована в режимі випуску, існує можливість помилок, які б не проявлялися в режимі налагодження. Є й інші питання, які стосуються цієї проблеми: Загальні причини помилок у версії випуску відсутні в режимі налагодження

Це траплялося зі мною один чи два рази, коли простий додаток консолі працював би чудово в режимі налагодження, але, враховуючи той самий вхід, помилка в режимі випуску. Ці помилки НАДКРИТИ важко налагоджувати (за визначенням режиму випуску, як не дивно).


Щоб продовжити, ось стаття, яка наводить приклад помилки випуску: codeproject.com/KB/trace/ReleaseBug.aspx
Roly

Але проблема все ж таки, якщо програма тестована та затверджена з налаштуваннями налагодження, навіть якщо вона придушує помилки, якщо це спричиняє збій збірки версії під час розгортання.
Øyvind Bråthen

4

Я б сказав, що 1) багато в чому залежить від вашої реалізації. Зазвичай різниця не така вже й величезна. Я робив багато вимірювань і часто не міг бачити різниці. Якщо ви використовуєте некерований код, безліч величезних масивів і подібних матеріалів, різниця в продуктивності трохи більша, але не інший світ (як, наприклад, у C ++). 2) Зазвичай у коді випуску відображається менше помилок (більша толерантність), отже, комутатор повинен справно працювати.


1
Для коду, який пов'язаний з IO, збірка випуску може бути не швидшою, ніж налагодження.
Річард

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
здається, що в режимі випуску іноді перші елементи списку не пронумеровані правильно. Також деякі елементи в списку дублюються. :)
Джан Паоло
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.