Различия в производительности между сборками отладки и выпуска


Я должен признать, что обычно я не беспокоился о переключении между Debug и релиз настройки в моей программе, и я обычно использую его для Debug конфигурация, даже когда программы фактически развернуты на месте клиентов.

насколько я знаю, единственное различие между этими конфигурациями, если вы не измените его вручную, это Debug есть DEBUG постоянная определено, и релиз есть оптимизировать код проверка.

Так что мои вопросы на самом деле двоякие:

  1. есть большая разница в производительности между этими двумя конфигурациями. Есть ли какой-либо конкретный тип кода, который вызовет большие различия в производительности здесь, или это на самом деле не так важно?

  2. есть ли какой-либо тип кода, который будет работать нормально под Debug конфигурация, которая может потерпеть неудачу при релиз конфигурации, или вы можете быть уверены, что код, который проверен и работает нормально, под Debug конфигурация также будет работать нормально в конфигурации выпуска.

8 260

8 ответов:

компилятор C# сам по себе не сильно изменяет испускаемый IL в сборке выпуска. Примечательно, что он больше не выдает коды операций NOP, которые позволяют установить точку останова на фигурной скобке. Большой-это оптимизатор, встроенный в JIT-компилятор. Я знаю, что он делает следующие оптимизации:

  • способ подстановки. Вызов метода заменяется введением кода метода. Это большой, он делает свойства аксессоры по существу бесплатный.

  • выделение регистра процессора. Локальные переменные и аргументы метода могут храниться в регистре ЦП, не возвращаясь (или реже) обратно в кадр стека. Это большой, заметный для того, чтобы сделать отладку оптимизированного кода настолько сложной. И отдавая летучие ключевое слово смысл.

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

  • развертывание циклов. Петли с маленькими телами улучшаются за счет повторения кода до 4 раз в теле и меньшего количества циклов. Снижает стоимость ветвления и улучшает супер-скалярные параметры выполнения процессора.

  • устранение мертвого кода. Утверждение типа if (false) { / .../} полностью устраняется. Это может произойти из-за постоянного складывания и встраивание. В других случаях JIT-компилятор может определить, что код не имеет возможных побочных эффектов. Именно эта оптимизация делает профилирование кода таким сложным.

  • поднимая код. Код внутри цикла, на который не влияет цикл, может быть перемещен из цикла. Оптимизатор компилятора C будет тратить гораздо больше времени на поиск возможностей для подъема. Это однако дорогостоящая оптимизация из-за необходимого анализа потока данных и дрожания не может позволить себе время, поэтому только поднимает очевидные случаи. Заставляя программистов .NET писать лучший исходный код и поднимать себя.

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

  • постоянный складной. x = 1 + 2; становится x = 3; Этот простой пример рано пойман компилятором, но происходит в JIT-время, когда другие оптимизации делают это возможным.

  • распространение копию. х = а; у = Х; становится y = a; это помогает зарегистрировать распределитель принимать более обоснованные решения. Это большое дело в дрожании x86, потому что у него мало регистров для работы. Имея его выбрать правильные из них имеет решающее значение для перфорация.

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

эффективный результат этих оптимизаций на время выполнения вашей программы часто зависит от кода, который выполняется в другом месте. Чтение файла, выполнение запроса dBase и т. п. Делая работу JIT оптимизатор делает полностью невидимым. Это не возражает, хотя :)

JIT оптимизатор довольно надежный код, в основном потому, что он был поставлен на тест миллионы раз. Крайне редко возникают проблемы в Выпустить версию сборки вашей программы. Однако это происходит. Как у x64, так и у x86 jitters были проблемы со структурами. Дрожание x86 имеет проблемы с согласованностью с плавающей запятой, создавая тонко различные результаты, когда промежуточные элементы вычисления с плавающей запятой хранятся в регистре FPU с 80-битной точностью вместо того, чтобы усекаться при сбросе в память.

  1. Да, есть много различий в производительности, и они действительно применяются во всем коде. Отладка делает очень мало оптимизации производительности, и режим выпуска очень много;

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

пример кода фреймворка, который зависит от DEBUG постоянный Debug.Assert() метод, который атрибут [Conditional("DEBUG)"] определенными. Это означает, что он также зависит от DEBUG постоянный и это не входит в сборку.

Это сильно зависит от характера вашего приложения. Если ваше приложение является UI-heavy, вы, вероятно, не заметите никакой разницы, так как самый медленный компонент, подключенный к современному компьютеру, - это пользователь. Если вы используете некоторые анимации пользовательского интерфейса, вы можете проверить, можете ли вы заметить какое-либо заметное отставание при запуске в DEBUG build.

однако, если у вас есть много вычислений-тяжелых вычислений, то вы заметите различия (может быть до 40%, как упоминал @Pieter, хотя это будет зависеть от характера расчетов).

Это в основном компромисс дизайна. Если вы выпускаете под DEBUG build, то если пользователи испытывают проблемы, вы можете получить более значимую трассировку и вы можете сделать гораздо более гибкую диагностику. Выпуская в DEBUG build, вы также избегаете оптимизатора, производящего obscure Heisenbugs.

  • мой опыт показывает, что средние или большие приложения заметно более отзывчивы в сборке выпуска. Дайте ему попробовать с вашим приложением и посмотреть, как он себя чувствует.

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

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

по моему опыту, худшее, что вышло из режима выпуска, - это неясные "ошибки выпуска". Поскольку IL (промежуточный язык) оптимизирован в режиме выпуска, существует возможность ошибок, которые не проявились бы в режиме отладки. Есть и другие вопросы SO, охватывающие эту проблему: общие причины ошибок в версии выпуска не присутствуют в режиме отладки

Это случилось со мной один или два раза, где простое консольное приложение будет работать отлично отлично в режиме отладки, но с учетом того же ввода, будет ошибка в режиме выпуска. Эти ошибки чрезвычайно трудно отлаживать (по определению режима выпуска, по иронии судьбы).

Я бы сказал, что 1) во многом зависит от вашей реализации. Обычно разница не так уж и велика. Я сделал много измерений, и часто я не видел разницы. Если использовать неуправляемый код, множество огромных массивов и тому подобное, разница в производительности немного больше, но не в другой мир (как в C++). 2) Обычно в коде выпуска отображается меньше ошибок (более высокий допуск), поэтому коммутатор должен работать нормально.

    **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:
    More optimized code
    Some additional instructions are removed and developer can’t set a breakpoint on every source code line.
   1) Less memory is used by the source code at runtime.
   2) Scripts & images downloaded by webresource.axd are cached.
   3) It has small size, and runs fast.
   4) Scripts & images downloaded by webresource.axd are cached.
   5) It has small size, and runs fast.