Поделиться через


Обзор профилирования

Профилировщик — это средство, которое отслеживает выполнение другого приложения. Профилировщик clR — это библиотека динамической компоновки (DLL), состоящая из функций, получающих сообщения и отправляющих сообщения в среду 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-компиляция и события 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, которая выполняется в составе подсистемы выполнения среды cl language. В результате код в библиотеке 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 профилирования.