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

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

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

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

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

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

Ответы

Ответ 1

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

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

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

  • Исправление проверки индекса массива. Важная оптимизация при работе с массивами (все классы .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, когда другие оптимизации делают это возможным.

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

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

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

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

Ответ 2

  • Да, есть много различий в производительности, и они действительно применяются по всему вашему коду. Отладка очень мало оптимизирует производительность и очень эффективна,

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

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

Ответ 3

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

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

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

Ответ 4

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

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

Ответ 5

Вы никогда не должны выпускать сборку .NET Debug в производство. Он может содержать уродливый код для поддержки Edit-and-Continue или кто знает, что еще. Насколько мне известно, это происходит только в VB, а не в С# (примечание: исходная запись отмечена С#), но она все равно должна дать основание для паузы в отношении того, что Microsoft считает, что им разрешено делать сборку Debug. Фактически, до .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 и добавляя их в статический список удерживая блокировку), я, конечно же, не хочу поддержки такого рода отладки в производственной среде!

Ответ 6

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

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

Ответ 7

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

Ответ 8

    **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.