Как производительность компиляции .NET JIT (включая динамические методы) влияет на параметры отладки изображения компилятора С#?
Я пытаюсь оптимизировать свое приложение для того, чтобы он работал хорошо сразу после его запуска. На данный момент его распространение содержит 304 двоичных файла (включая внешние зависимости) на общую сумму 57 мегабайт. Это приложение WPF, использующее в основном доступ к базе данных без каких-либо значительных вычислений.
Я обнаружил, что для большинства операций конфигурация Debug обеспечивает более высокий (~ 5 раз) раз, так как они выполняются в первый раз в течение всего жизненного цикла процесса приложения. Например, открытие определенного экрана в приложении занимает 0,3 секунды для NGENed Debug, 0,5 секунды для JITted Debug, 1,5 секунды для NGENed Release и 2,5 секунды для JITted Release.
Я понимаю, что разрыв в времени компиляции JIT вызван компилятором JIT, применяющим более агрессивные оптимизации для двоичных файлов Release. Из того, что я могу сказать, конфигурации Debug и Release отличаются переключателями /p:DebugType
и /p:Optimize
, переданными компилятору С#, но я вижу тот же самый разрыв в производительности, даже если я создаю приложение с /p:Configuration=Release /p:DebugType=full /p:Optimize=false
- то есть то же самое Параметры отладки изображения, как в /p:Configuration=Debug
.
Я подтверждаю, что параметры были применены, посмотрев на DebuggableAttribute
, примененный к результирующей сборке. Наблюдая за выходом NGEN, я вижу, что <debug>
добавлен к именам компилируемых компиляций - как NGEN различает отладочные и не-отладочные сборки? Проверяемая операция использует генерацию динамического кода - какой уровень оптимизации применяется к динамическому коду?
Примечание. Я использую 32-разрядную структуру из-за внешних зависимостей. Должен ли я ожидать разные результаты на x64?
Примечание. Я также не использую условную компиляцию. Таким образом, скомпилированный источник одинаковый для обеих конфигураций.
Ответы
Ответ 1
Если, как вы говорите, у вас есть 304 сборки для загрузки, то это, вероятно, является причиной медленного использования вашего приложения.
Это похоже на чрезвычайно большое количество сборок для загрузки.
Каждый раз, когда CLR достигает кода из другой сборки, которая еще не загружена в AppDomain, она должна загружать ее с диска.
Вы можете использовать ILMerge для объединения некоторых из этих сборок. Это уменьшит задержку при загрузке сборок с диска (вы берете один, больше, диск попадает вверх).
Это может потребовать некоторых экспериментов, поскольку не все нравится сливаться (особенно те, которые используют Reflection, и зависят от имени файла сборки, который никогда не меняется). Это может также привести к очень большим сбоям.
Ответ 2
Хорошо, здесь несколько вопросов.
Из того, что я могу сказать, конфигурации Debug и Release отличаются /p: DebugType и /p: оптимизировать переключатели, переданные компилятору С#, но я см. тот же самый разрыв в производительности, даже если я создаю приложение с помощью /p: Конфигурация = Release/p: DebugType = full/p: Optimize = false - это это те же параметры отладки изображения, что и в /p: Configuration = Debug.
Несмотря на то, что тик-боксы одинаковы, переход в режим деблокирования также приводит к удалению некоторых внутренних путей кода, например Debug.Assert()
(сильно используется во внутреннем коде Microsoft). Поэтому они не оцениваются во время выполнения, что приводит к некоторому повышению производительности. DebugType=full
генерирует PDB, соответствующий коду, с которым он был скомпилирован, поэтому сам по себе не является производительным ударом. Если PDB развернут, код обработки исключений будет использовать PDB для предоставления более полезных стеков стека против скомпилированного кода. Режим деблокирования также внутренне запускает некоторые улучшения памяти, поскольку для отладки используются отладочные версии.
NGEN - это инструмент, используемый для "потенциальной" оптимизации приложения. Он оптимизирует код для работы с конкретным компьютером, на котором вы находитесь. Но это может иметь недостатки, поскольку компилятор JIT может внести изменения в макет кода в памяти, тогда как NGEN более статичен по своей природе.
Что касается 32-разрядных (x86) зависимостей, ваше приложение теперь будет работать в режиме x86. Если зависимость имела и доступная версия x86 и x64, и если ваше приложение скомпилировано в режиме компиляции "Любой процессор", компилятор JIT автоматически переключится между 2. NGEN будет генерировать только конкретную версию для текущего компьютера. Так что если вы сделали NGEN, а затем распространили ее, она будет работать только для конкретной архитектуры, скомпилированной против.
Если вы не используете функции условной компиляции, на самом деле не имеет значения, переключитесь с Debug на Release. Но вы увидите преимущество производительности в Release.
С NGEN я предлагаю вам внимательно протестировать преимущества над 2. Это не всегда приводит к повышению производительности.
Ответ 3
Запускаете ли вы его под отладчиком ('F5') или без отладчика ('ctrl + F5')? Если первые, убедитесь, что Инструменты → Параметры → Отладка → "Подавить оптимизацию JIT при загрузке модуля" не отмечены