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

280

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

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

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

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

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

Ойвинд Бротен
источник
1
Связанный: stackoverflow.com/questions/33871181/…
BlueRaja - Дэнни Пфлугхофт

Ответы:

511

Сам компилятор 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 - довольно надежный код, в основном потому, что он был проверен миллионы раз. Чрезвычайно редко возникают проблемы в версии выпуска вашей программы. Это случается однако. У jitters 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 : «Ключевое слово volatile можно применять только к полям класса или структуры. Локальные переменные не могут быть объявлены как volatile».
Крис Вандермоттен,
8
как скромная поправка, я полагаю, что на самом деле разница между этими вариантами Debugи Releaseсборками в этом отношении заключается в флажке «оптимизировать код», который обычно включен, Releaseно выключен Debug. Просто чтобы убедиться, что читатели не начинают думать, что между двумя конфигурациями сборки есть «волшебные», невидимые различия, выходящие за рамки того, что можно найти на странице свойств проекта в Visual Studio.
Чиккодоро
3
Возможно, стоит упомянуть, что практически ни один из методов System.Diagnostics.Debug ничего не делает в отладочной сборке. Также переменные не завершаются довольно быстро, чтобы увидеть ( stackoverflow.com/a/7165380/20553 ).
Мартин Браун
23
  1. Да, есть много различий в производительности, и они действительно применяются во всем вашем коде. Debug очень мало оптимизирует производительность, а режим релиза очень сильно;

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

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

Питер ван Гинкель
источник
2
Это все правда, но вы могли бы измерить разницу? Или заметили разницу при использовании программы? Конечно, я не хочу призывать кого-либо выпускать свое программное обеспечение в режиме отладки, но вопрос был в том, есть ли огромная разница в производительности, и я этого не вижу.
testalino
2
Также стоит отметить, что отладочные версии соотносятся с исходным исходным кодом в гораздо большей степени, чем версии выпуска. Если вы думаете (хотя и маловероятно), что кто-то может попытаться перепроектировать ваши исполняемые файлы, вы не захотите упростить для них развертывание отладочных версий.
Джуверон
2
@testalino - Ну, в наши дни это сложно. Процессоры стали настолько быстрыми, что пользователь едва ли ждет, пока процесс на самом деле выполнит код из-за действий пользователя, так что это все относительно. Однако, если вы на самом деле делаете какой-то длительный процесс, да, вы заметите. Следующий код работает , например , 40% медленнее при DEBUG: AppDomain.CurrentDomain.GetAssemblies().Sum(p => p.GetTypes().Sum(p1 => p1.GetProperties().Length)).
Питер ван Гинкель
2
Кроме того, если вы asp.netиспользуете debug вместо релиза, некоторые скрипты могут быть добавлены на вашу страницу, например: MicrosoftAjax.debug.jsэто имеет около 7 тыс. Строк.
BrunoLM
13

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

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

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

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

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

Дэн Брайант
источник
9

Вы никогда не должны выпускать сборку .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удержание блокировки

Джейсон Кресоваты
источник
Я выпускал сборки Debug много раз и никогда не видел проблем. Единственное отличие, возможно, заключается в том, что наше серверное приложение не является веб-приложением, поддерживающим большое количество пользователей. Но это приложение на стороне сервера с очень высокой загрузкой. Из моего опыта разница между Debug и Release кажется абсолютно теоретической. Я никогда не видел практической разницы ни с одним из наших приложений.
Сэм Голдберг,
5

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

Это случалось со мной один или два раза, когда простое консольное приложение прекрасно работало в режиме отладки, но при точно таком же вводе в режиме выпуска происходило сбои. Эти ошибки ЧРЕЗВЫЧАЙНО трудно устранить (по иронии судьбы, по определению режима Release).

Roly
источник
Чтобы продолжить, вот статья, которая дает пример ошибки выпуска: codeproject.com/KB/trace/ReleaseBug.aspx
Roly
Тем не менее проблема заключается в том, что приложение протестировано и одобрено с параметрами отладки, даже если оно подавляет ошибки, если это приводит к сбою сборки выпуска во время развертывания.
Ойвинд Бротен
4

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

testalino
источник
1
Для кода, который связан с вводом-выводом, сборка выпуска может быть не такой быстрой, как отладка.
Ричард
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
Кажется, что в режиме выпуска иногда первые элементы списка не нумеруются правильно. Также некоторые элементы в списке дублируются. :)
Джан Паоло