Различия в производительности между сборками отладки и выпуска
Я должен признать, что обычно я не беспокоился о переключении между конфигурациями Debug и Release в моей программе, и я обычно выбирал конфигурацию Debug, даже если программы фактически развернуты у клиентов.
Насколько я знаю, единственное различие между этими конфигурациями, если вы не измените его вручную, состоит в том, что Debug имеет DEBUG
константа определена, а Release имеет проверенный код оптимизации.
Так что мои вопросы на самом деле двоякие:
Есть много различий в производительности между этими двумя конфигурациями. Есть ли какой-то конкретный тип кода, который будет вызывать большие различия в производительности, или это на самом деле не так важно?
Существуют ли какие-либо типы кода, которые будут нормально работать в конфигурации Debug, которые могут не работать в конфигурации Release, или вы можете быть уверены, что код, который протестирован и работает нормально в конфигурации Debug, также будет работать нормально в конфигурации Release.
9 ответов
Сам компилятор C# не сильно изменяет выдаваемый IL в сборке Release. Примечательно, что он больше не генерирует коды операций NOP, которые позволяют вам установить точку останова на фигурной скобке. Большой - это оптимизатор, встроенный в JIT-компилятор. Я знаю, что это делает следующие оптимизации:
Метод встраивания. Вызов метода заменяется введением кода метода. Это большой, он делает доступ к свойствам по существу бесплатно.
Распределение регистров процессора. Локальные переменные и аргументы метода могут храниться в регистре ЦП, и никогда (или реже) не сохраняться обратно во фрейм стека. Это большая проблема, которая делает процесс отладки оптимизированного кода таким сложным. И придание изменчивому ключевому слову значения.
Исключение проверки индекса массива. Важная оптимизация при работе с массивами (все классы коллекций.NET используют массив внутри). Когда JIT-компилятор может проверить, что цикл никогда не индексирует массив вне границ, он устраняет проверку индекса. Большой.
Разматывание петли. Циклы с маленькими телами улучшаются, повторяя код до 4 раз в теле и зацикливаясь меньше. Снижает стоимость ветвления и улучшает суперскалярные параметры исполнения процессора.
Устранение мертвого кода. Утверждение типа if (false) {/.../} полностью исключается. Это может произойти из-за постоянного складывания и наклона. В других случаях JIT-компилятор может определить, что в коде нет возможных побочных эффектов. Эта оптимизация делает профилирование кода таким сложным.
Подъем кода. Код внутри цикла, на который цикл не влияет, может быть удален из цикла. Оптимизатор компилятора Си будет тратить гораздо больше времени на поиск возможностей для подъема. Однако это дорогостоящая оптимизация из-за необходимого анализа потока данных, и джиттер не может позволить себе время, поэтому только поднимает очевидные случаи. Заставлять.NET-программистов лучше писать исходный код и самим подниматься.
Устранение общего подвыражения. х = у + 4; z = y + 4; становится z = x; Довольно часто встречается в таких выражениях, как dest[ix+1] = src[ix+1]; написано для удобства чтения без введения вспомогательной переменной. Не нужно ставить под угрозу читабельность.
Постоянное складывание. х = 1 + 2; становится х = 3; Этот простой пример обнаруживается компилятором на ранней стадии, но происходит во время JIT, когда другие оптимизации делают это возможным.
Копирование распространения. х = а; у = х; становится у = а; Это помогает распределителю регистра принимать лучшие решения. Это большая проблема в джиттере x86, потому что у него мало регистров для работы. Правильный выбор имеет решающее значение для перфекта.
Это очень важные оптимизации, которые могут иметь большое значение, когда, например, вы профилируете сборку Debug своего приложения и сравниваете ее со сборкой Release. Это действительно имеет значение, хотя, когда код находится на вашем критическом пути, от 5 до 10% кода, который вы пишете, фактически влияет на производительность вашей программы. Оптимизатор JIT не настолько умен, чтобы заранее знать, что критично, он может применять только "поворот на одиннадцать" для всего кода.
Эффективный результат этих оптимизаций для времени выполнения вашей программы часто зависит от кода, который выполняется в другом месте. Чтение файла, выполнение запроса к базе данных и т. Д. Выполнение работы оптимизатором JIT делает полностью незаметным. Это не против, хотя:)
Оптимизатор JIT - это довольно надежный код, в основном потому, что его тестировали миллионы раз. Очень редко возникают проблемы в версии сборки вашей версии. Это случается однако. У xit и x86 jitters были проблемы со структурами. Джиттер x86 имеет проблемы с согласованностью с плавающей запятой, приводя к несколько иным результатам, когда промежуточные значения вычисления с плавающей запятой хранятся в регистре FPU с 80-битной точностью вместо усечения при сбросе в память.
Да, есть много различий в производительности, и они действительно применяются во всем вашем коде. Debug очень мало оптимизирует производительность, а режим релиза очень сильно;
Только код, который опирается на
DEBUG
Константа может работать по-разному в сборке релиза. Кроме того, вы не должны видеть никаких проблем.
Пример кода платформы, который зависит от DEBUG
константа является Debug.Assert()
метод, который имеет атрибут [Conditional("DEBUG)"]
определены. Это означает, что это также зависит от DEBUG
постоянный, и это не входит в сборку релиза.
Это сильно зависит от характера вашего приложения. Если ваше приложение сильно загружено с помощью пользовательского интерфейса, вы, вероятно, не заметите никакой разницы, поскольку самым медленным компонентом, подключенным к современному компьютеру, является пользователь. Если вы используете некоторые анимации пользовательского интерфейса, вы можете проверить, можете ли вы заметить заметную задержку при запуске в сборке DEBUG.
Однако, если у вас много вычислений, требующих большого объема вычислений, вы заметите различия (они могут достигать 40%, как упоминал @Pieter, хотя это будет зависеть от характера вычислений).
Это в основном дизайн компромисс. Если вы выпускаете под сборкой DEBUG, то, если у пользователей возникают проблемы, вы можете получить более значимый след и вы можете сделать гораздо более гибкую диагностику. Выпуская в сборке DEBUG, вы также избегаете оптимизатора, создающего малоизвестные гейзены.
По моему опыту, приложения среднего размера или более крупного размера заметно быстрее реагируют на сборку выпуска. Попробуйте приложение и посмотрите, как оно выглядит.
Одна вещь, которая может укусить вас в сборках Release, заключается в том, что код отладочной сборки может иногда подавлять условия гонки и другие связанные с многопоточностью ошибки. Оптимизированный код может привести к переупорядочению команд, а более быстрое выполнение может усугубить определенные условия гонки.
Вы никогда не должны выпускать сборку.NET Debug в производство. Он может содержать некрасивый код для поддержки редактирования и продолжения или кто знает что еще. Насколько я знаю, это происходит только в VB, а не в C# (примечание: исходное сообщение помечено как C#), но оно все же должно дать повод задуматься над тем, что, по мнению Microsoft, им разрешено делать со сборкой Debug. Фактически, до.NET 4.0, код VB теряет память, пропорциональную количеству экземпляров объектов с событиями, которые вы создаете для поддержки редактирования и продолжения. (Хотя сообщается, что это исправлено в https://connect.microsoft.com/VisualStudio/feedback/details/481671/vb-classes-with-events-are-not-garbage-collected-when-debugging сгенерированный код выглядит противно, создавая WeakReference
объекты и добавление их в статический список, удерживая блокировку) Я, конечно, не хочу никакой поддержки такого рода в производственной среде!
По моему опыту, худшее, что вышло из режима Release - это скрытые "ошибки выпуска". Поскольку IL (промежуточный язык) оптимизирован в режиме выпуска, существует возможность ошибок, которые не проявились бы в режиме отладки. Существуют и другие вопросы, связанные с этой проблемой: общие причины ошибок в версии выпуска отсутствуют в режиме отладки
Это случалось со мной один или два раза, когда простое консольное приложение прекрасно работало в режиме отладки, но при точно таком же вводе в режиме выпуска происходило сбои. Эти ошибки ЧРЕЗВЫЧАЙНО трудно устранить (по иронии судьбы, по определению режима Release).
Я бы сказал, что 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.
Я знаю, что мой ответ ОЧЕНЬ поздно, и мой ответ не совсем то, что вы хотите, но я подумал, что какой-нибудь надежный и простой пример для игры был бы хорош. В любом случае, этот фрагмент кода приводит к ОГРОМНОЙ разнице между отладкой и выпуском . Код написан на C ++ в Visual Studio 2019. Код такой:
#include <iostream>
using namespace std;
unsigned long long fibonacci(int n)
{
return n < 2 ? n : (fibonacci(n - 1) + fibonacci(n - 2));
}
int main()
{
int x = 47;
cout << "Calculating..." << endl;
cout << "fib(" << x << ") = " << fibonacci(x) << endl;
}