Общие сведения о профилировании

Профилировщик — это средство, которое отслеживает выполнение другого приложения. Профилировщик среды CLR — это библиотека DLL, содержащая функции, которые получают сообщения из среды CLR и отправляют сообщения в среду CLR с помощью API профилирования. Библиотека DLL профилировщика загружается средой CLR во время выполнения.

Традиционные средства профилирования основное внимание уделяют измерению выполнения приложения. То есть они измеряют время, затраченное на каждую функцию, или использование памяти приложением за период времени. API профилирования предназначен для более широкого класса диагностических средств, таких как служебные программы с покрытием кода и расширенные средства отладки. Сфера их применения — вся диагностика в природе. API профилирования не только измеряет, но также наблюдает за выполнением приложения. По этой причине API профилирования никогда не должен использоваться самим приложением, и выполнение приложения не должно ни зависеть от профилировщика, ни подвергаться его влиянию.

Для профилирования приложения среды CLR требуется дополнительная поддержка по сравнению с профилированием стандартно скомпилированного машинного кода. Это связано с тем, что среда CLR представляет такие понятия, как домены приложений, сборка мусора, обработка управляемых исключений, JIT-компиляция кода (преобразование общего промежуточного языка или CIL, код в машинный код) и аналогичные функции. Механизмы традиционного профилирования не могут обнаруживать эти возможности или предоставлять полезные сведения о них. API профилирования эффективно предоставляет эти отсутствующие сведения с минимальным влиянием на производительность среды CLR и профилируемого приложения.

JIT-компиляция во время выполнения обеспечивает прекрасные возможности для профилирования. API профилирования позволяет профилировщику изменять поток кода CIL в памяти для подпрограммы перед компиляцией JIT-кода. Таким образом, профилировщик может динамически добавлять код инструментирования в определенные подпрограммы, требующие более глубокого анализа. Хотя такой подход возможен в обычных сценариях, его гораздо проще реализовать для среды CLR с помощью API профилирования.

API профилирования

Как правило, API профилирования используется для записи профилировщика кода, которая является программой, которая отслеживает выполнение управляемого приложения.

API профилирования используется библиотекой DLL профилировщика, которая загружается в один процесс с профилируемым приложением. Библиотека DLL профилировщика реализует интерфейс обратного вызова (ICorProfilerCallback в платформа .NET Framework версии 1.0 и 1.1, ICorProfilerCallback2 в версии 2.0 и более поздних версиях). Среда CLR вызывает методы этого интерфейса для уведомления профилировщика о событиях в процессе профилирования. Профилировщик может вернуться в среду выполнения с помощью методов в интерфейсах ICorProfilerInfo и ICorProfilerInfo2 для получения сведений о состоянии профилированного приложения.

Примечание.

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

На следующем рисунке показано, как библиотека DLL профилировщика взаимодействует с профилируемым приложением и средой CLR.

Снимок экрана: архитектура профилирования.

Интерфейсы уведомлений

Интерфейсы уведомлений iCorProfilerCallback и ICorProfilerCallback2 можно рассматривать как интерфейсы уведомлений. Эти интерфейсы состоят из таких методов, как ClassLoadStarted, ClassLoadFinished и JITCompilationStarted. Каждый раз, когда среда CLR загружает или выгружает класс, компилирует функцию и т. д., она вызывает соответствующий метод в интерфейсе ICorProfilerCallback или ICorProfilerCallback2 профилировщика.

Например, профилировщик может измерять производительность кода с помощью двух функций уведомлений: FunctionEnter2 и FunctionLeave2. Он просто устанавливает метки времени для каждого уведомления, собирает результаты и выводит список, в котором указывается, на какие функции было затрачено больше ресурсов ЦП или физического времени во время выполнения приложения.

Интерфейсы для извлечения сведений

Другими основными интерфейсами, участвующими в профилировании, являются ICorProfilerInfo и ICorProfilerInfo2. Профилировщик вызывает эти интерфейсы по мере необходимости для получения дополнительных сведений, помогающих выполнить анализ. Например, когда среда CLR вызывает функцию FunctionEnter2 , она предоставляет идентификатор функции. Профилировщик может получить дополнительные сведения об этой функции, вызвав метод ICorProfilerInfo2::GetFunctionInfo2 для обнаружения родительского класса функции, его имени и т. д.

Поддерживаемые возможности

API профилирования предоставляет сведения о различных событиях и действиях, которые происходят в среде CLR. Эти сведения можно использовать для мониторинга внутренней работы процессов и анализа производительности приложения .NET Framework.

API профилирования извлекает сведения о следующих действиях и событиях, происходящих в среде CLR.

  • События запуска и завершения работы среды CLR.

  • События создания и завершения работы домена приложения.

  • События загрузки и выгрузки сборки.

  • События загрузки и выгрузки модуля.

  • События создания и удаления таблицы VTable COM.

  • События JIT-компиляции и пошагового выполнения кода.

  • События загрузки и выгрузки класса.

  • События создания и удаления потока.

  • События входа и выхода функции.

  • Исключения.

  • Переходы между выполнением управляемого и неуправляемого кода.

  • Переходы между различными контекстами среды выполнения.

  • Сведения о приостановках среды выполнения.

  • Сведения о действиях сборки мусора и кучи в памяти времени выполнения.

API профилирования можно вызывать из любого (неуправляемого) языка, совместимого с COM.

Этот API является эффективным с точки зрения потребления ресурсов ЦП и памяти. Профилирование не влечет за собой изменения профилируемого приложения, которые могут привести к недостоверным результатам.

API профилирования полезен для профилировщиков как с выборкой, так и без выборки. Профилировщик выборки проверяет профиль на регулярных часах, например, в 5 миллисекундах друг от друга. Профилировщик без выборки сообщает о событии синхронно с потоком, вызывающим событие.

Неподдерживаемая функциональность

API профилирования не поддерживает следующие функциональные возможности.

  • Неуправляемый код, который необходимо профилировать с помощью стандартных методов Win32. Однако профилировщик среды CLR включает события переходов для определения границ между управляемым и неуправляемым кодом.

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

  • Проверка привязок, поскольку API профилирования не предоставляет эти сведения. Среда CLR предоставляет существенную поддержку для проверки границ всего управляемого кода.

  • Удаленное профилирование, которое не поддерживается по следующим причинам.

    • Удаленное профилирование увеличивает время выполнения. При использовании интерфейсов профилирования необходимо минимизировать время выполнения, чтобы оно не слишком сильно сказывалось на результатах профилирования. Это особенно важно при мониторинге производительности. Тем не менее удаленное профилирование не является ограничением при использовании интерфейсов профилирования для мониторинга использования памяти или для получения сведений времени выполнения о кадрах стека, объектах и т. п.

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

Потоки уведомлений

В большинстве случаев поток, который создает событие, также выполняет уведомления. Такие уведомления (например, FunctionEnter и FunctionLeave) не требуют явного ThreadIDпредоставления. Кроме того, профилировщик может использовать локальное хранилище потока для хранения и обновления своих блоков анализа вместо индексирования этих блоков в глобальном хранилище на основе ThreadID затронутого потока.

Обратите внимание, что эти обратные вызовы не сериализуются. Пользователи должны защищать свой код, путем создания потокобезопасных структур данных и путем блокировки кода профилировщика в тех случаях, когда необходимо предотвратить параллельный доступ из нескольких потоков. Таким образом, в некоторых случаях можно получить необычную последовательность обратных вызовов. Например, предположим, что управляемое приложение порождает два потока, выполняющие идентичный код. В этом случае можно получить событие ICorProfilerCallback::JITCompilationStarted для одной функции и FunctionEnter обратного вызова из другого потока перед получением обратного вызова ICorProfilerCallback::JITCompilationFinished . В этом случае пользователь получит обратный вызов FunctionEnter для функции, которая могла быть не полностью JIT-скомпилирована.

Безопасность

Библиотека DLL профилировщика — это неуправляемая библиотека DLL, которая выполняется в рамках подсистемы выполнения среды CLR. В результате на код в библиотеке DLL профилировщика DLL не налагаются ограничения управления доступом для управляемого кода. Для библиотеки DLL профилировщика действуют только ограничения, накладываемые операционной системой на пользователя, запускающего профилируемое приложение.

Разработчики профилировщика должны принять соответствующие меры предосторожности, чтобы избежать проблем, связанных с безопасностью. Например, во время установки библиотека DLL профилировщика должна добавляться в список управления доступом (ACL), чтобы злоумышленник не мог изменить ее.

Объединение управляемого и неуправляемого кода в коде профилировщика

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

Обзор API профилирования среды CLR может создать впечатление, что можно написать профилировщик, содержащий управляемые и неуправляемые компоненты, которые вызывают друг друга посредством COM-взаимодействия или непрямых вызовов.

Хотя это возможно с точки зрения проектирования, API профилирования не поддерживает управляемые компоненты. Профилировщик среды CLR должен быть полностью неуправляемым. Попытки объединить управляемый и неуправляемый код в профилировщике среды CLR могут привести к нарушениям прав доступа, сбоям программы или взаимоблокировкам. Управляемые компоненты профилировщика будут возвращать события обратно их неуправляемым компонентам, что будет затем вызывать управляемые компоненты снова, и таким образом будут создаваться циклические ссылки.

Единственное расположение, где профилировщик CLR может безопасно вызывать управляемый код, находится в теле метода общего промежуточного языка (CIL). Рекомендуемая практика изменения текста CIL — использовать методы повторной компиляции JIT в интерфейсе ICorProfilerCallback4 .

Для изменения CIL также можно использовать более старые методы инструментирования. Перед завершением JIT-компиляции функции профилировщик может вставить управляемые вызовы в текст метода CIL, а затем JIT-компиляцию (см . метод ICorProfilerInfo::GetILFunctionBody ). Этот способ можно успешно использовать для выборочного инструментирования управляемого кода или для сбора статистики и данных производительности касательно JIT.

Кроме того, профилировщик кода может вставлять собственные перехватчики в текст CIL каждой управляемой функции, которая вызывает неуправляемый код. Этот способ можно использовать для инструментирования и покрытия. Например, профилировщик кода может вставлять перехватчики инструментирования после каждого блока CIL, чтобы убедиться, что блок был выполнен. Изменение тела CIL метода является очень деликатной операцией, и необходимо учитывать множество факторов.

Профилирование неуправляемого кода

API профилирования среды CLR предоставляет минимальную поддержку профилирования неуправляемого кода. Обеспечивается следующую функциональность:

  • Перечисление цепочек стека. Эта возможность позволяет профилировщику кода определить границу между управляемым и неуправляемым кодом.

  • Определение, соответствует ли цепочка стека управляемому коду или машинному коду.

В .NET Framework версий 1.0 и 1.1 эти методы доступны через внутрипроцессное подмножество API отладки среды CLR. Они определяются в файле CorDebug.idl.

В платформа .NET Framework 2.0 и более поздних версиях для этой функции можно использовать метод ICorProfilerInfo2::D oStackSnapshot.

Использование модели COM

Хотя интерфейсы профилирования определяются как COM-интерфейсы, среда CLR в действительности не инициализирует модель COM для использования этих интерфейсов. Причина заключается в том, чтобы избежать необходимости задавать модель потоков с помощью функции CoInitialize до того, как управляемое приложение имело возможность указать нужную модель потоков. Аналогично, сам профилировщик не должен вызывать CoInitialize, поскольку он может выбрать потоковую модель, несовместимую с профилируемым приложением, что может привести к сбою приложения.

Стеки вызовов

API профилирования предоставляет два способа получения стеков вызова: метод моментальных снимков стека, который позволяет реже выполнять сбор стеков вызовов, и метод теневого стека, который отслеживает стек вызовов в каждый момент времени.

Моментальный снимок стека

Моментальный снимок стека — это трассировка стека потока в момент времени. API профилирования поддерживает трассировку управляемых функций в стеке, но оставляет трассировку неуправляемых функций собственному обходчику стека профилировщика.

Дополнительные сведения о программе профилировщика для обхода управляемых стеков см. в методе ICorProfilerInfo2::D oStackSnapshot в этом наборе документации и профилировщике Stack Walk в платформа .NET Framework 2.0: Основные сведения и дополнительные сведения.

Теневой стек

Слишком частое использование метода моментального снимка может быстро создавать проблемы производительности. Если вы хотите часто выполнять трассировки стека, профилировщик должен вместо этого создавать теневой стек с помощью вызовов исключений FunctionEnter2, FunctionLeave2, FunctionTailcall2 и ICorProfilerCallback2. Теневой стек всегда является текущим, и его можно быстро скопировать в хранилище каждый раз, когда требуется моментальный снимок стека.

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

Обратные вызовы и глубина стека вызовов

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

Заголовок Description
Настройка среды профилирования В этом разделе объясняется, как можно инициализировать профилировщик, установить уведомления о событиях и профилировать службу Windows.
Интерфейсы профилирования В этом разделе описываются неуправляемые интерфейсы, которые использует API профилирования.
Глобальные статические функции профилирования В этом разделе описываются неуправляемые глобальные статистические функции, которые использует API профилирования.
Перечисления профилирования В этом разделе описываются неуправляемые перечисления, которые использует API профилирования.
Структуры профилирования В этом разделе описываются неуправляемые структуры, которые использует API профилирования.