42
Глава.1 .Модель.выполнения.кода.в.среде.CLR
процесс отладки кода, но зато оптимизируя поток управления программой. Кроме
того, возможно, некоторые оценочные функции не выполняются во время отладки.
Однако IL-код меньше по размерам, и это уменьшает
результирующий размер EXE-
или DLL-файлов; кроме того, IL-код легче читать тем, кто обожает исследовать
IL-код, пытаясь понять, что именно породил компилятор (например, мне).
Кроме того, компилятор строит файл PDB (Program Database) только при
задании параметра
/debug(+/full/pdbonly)
. Файл PDB помогает отладчику
находить локальные переменные и связывать команды IL с исходным кодом.
Параметр
/debug:full
сообщает JIT-компилятору о том,
что вы намерены заняться
отладкой сборки; JIT-компилятор сохраняет информацию о том, какой машин-
ный код был сгенерирован для каждой команды IL. Это позволяет использовать
функцию JIT-отладки Visual Studio для связывания отладчика с уже работающим
процессом и упрощения отладки кода. Без параметра
/debug:full
компилятор по
умолчанию не сохраняет информацию о соответствии между IL и машинным кодом;
это несколько ускоряет компиляцию и сокращает затраты памяти. Если запустить
процесс в отладчике Visual Studio, то JIT-компилятор будет отслеживать инфор-
мацию о соответствии IL и машинного кода (независимо от
состояния параметра
/
debug
), если только вы не снимете флажок
Suppress.JIT.Optimization.On.Module.Load.
(Managed.Only)
в Visual Studio.
При создании нового проекта C# в Visual Studio в отладочной конфигурации
проекта устанавливаются параметры
/optimize
и
/debug:full
, а в конфигурации
выпуска – параметры
/optimize+
и
/debug:pdbonly
.
Разработчиков с опытом написания неуправляемого кода C или C++ обычно
беспокоит, как все это сказывается на быстродействии. Ведь неуправляемый код
компилируется для конкретного процессора и при вызове может просто вы-
полняться. В управляемой среде компиляция кода состоит из двух фаз. Сначала
компилятор проходит по исходному коду, выполняя
максимально возможную
работу по генерированию IL-кода. Но для выполенния кода сам IL-код должен
быть откомпилирован в машинные команды во время выполнения, что требует
выделения дополнительной памяти, которая не может использоваться совместно,
и дополнительных затрат процессорного времени.
Я и сам пришел к CLR с опытом программирования на C/C++ и меня сильно
беспокоили дополнительные затраты. Действительно, вторая стадия компиляции,
происходящая во время выполнения, замедляет выполнение и требует выделения
динамической памяти. Однако компания Microsoft основательно потрудилась над
оптимизацией, чтобы свести эти дополнительные затраты к минимуму.
Если вы тоже скептически относитесь к двухфазной компиляции, обязательно
попробуйте построить приложения и измерьте их быстродействие. Затем проделайте
то же самое с какими-нибудь нетривиальными
управляемыми приложениями, соз-
данными Microsoft или другими компаниями. Вас удивит, насколько эффективно
они выполняются.
Трудно поверить, но многие специалисты (включая меня) считают, что управляе-
мые приложения способны даже превзойти по производительности неуправляемые
43
Исполнение.кода.сборки
приложения. Это объясняется многими причинами. Например, в тот момент, когда
JIT-компилятор компилирует IL-код в машинный код во время выполнения, он
знает о среде выполнения больше, чем может знать неуправляемый компилятор.
Перечислим некоторые возможности повышения производительности управляемого
кода по сравнению с неуправляемым:
JIT-компилятор может определить, что приложение выполняется на процессоре
Intel Pentium 4, и сгенерировать машинный
код со специальными командами,
поддерживаемыми Pentium 4. Обычно неуправляемые приложения компилиру-
ются с самым общим набором команд и не используют специальные команды,
способные повысить эффективность приложения.
JIT-компилятор может определить, что некоторое условие на том компьютере,
на котором он выполняется, всегда оказывается ложным. Допустим, метод со-
держит следующий фрагмент:
if (numberOfCPUs > 1) {
...
}
Если компьютер оснащен всего одним процессором, то JIT-компилятор не
будет генерировать машинные команды для указанного фрагмента. В
этом слу-
чае машинный код оптимизируется для конкретной машины, а следовательно,
занимает меньше места и быстрее выполняется.
CLR может профилировать выполняемую программу и перекомпилировать IL
в машинный код в процессе выполнения. Перекомпилированный код реоргани-
зуется для сокращения ошибочного прогнозирования переходов на основании
наблюдаемых закономерностей выполнения. Текущие версии CLR такую воз-
можность не поддерживают, но возможно, она появится в будущих версиях.
Это лишь некоторые из причин, по которым в будущем управляемый код
может превзойти по производительности неуправляемый код.
Как я уже сказал,
в большинстве приложений достигается вполне неплохая производительность и в
будущем стоит ожидать ее улучшения.
Если ваши эксперименты показывают, что JIT-компилятор не обеспечивает
вашему приложению необходимого уровня производительности, возможно, вам
стоит воспользоваться утилитой
NGen exe
из пакета .NET Framework SDK. Эта
утилита компилирует весь IL-код сборки в машинный код и сохраняет его в файле
на диске. В момент выполнения при загрузке сборки CLR автоматически проверяет,
существует ли заранее откомпилированная версия сборки, и если существует — за-
гружает ее, так что компиляция во время выполнения уже не требуется. Учтите,
что
NGen exe
приходится осторожно строить предположения относительно фак-
тической среды выполнения, поэтому код,
генерируемый
NGen exe
, будет менее
оптимизированным, чем код JIT-компилятора.
Также при анализе производительности может пригодиться класс
System.
Runtime.ProfileOptimization
. Он заставляет CLR сохранить (в файле) инфор-