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


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

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

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

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

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

Этот обзор содержит следующие подразделы:

  • Интерфейс API для профилирования

  • Поддерживаемые функции

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

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

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

  • Неуправляемый код профилирования

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

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

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

  • Связанные разделы

Интерфейс 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;

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

  • событиях загрузки и выгрузки сборок;

  • событиях загрузки и выгрузки модулей;

  • событиях создания и удаления виртуальных таблиц COM;

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

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

  • событиях создания и удаления потоков;

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

  • исключения;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

К началу

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

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

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

К началу

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

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

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

К началу

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

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

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

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

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

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

К началу

Неуправляемый код профилирования

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

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

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

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

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

К началу

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

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

К началу

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

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

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

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

Дополнительные сведения о программировании обхода управляемого стека профилировщиком, см. в описании метода ICorProfilerInfo2::DoStackSnapshot в данном комплекте документации, а также статью Пошаговый анализ стеков при помощи профилировщика в среде платформы .NET Framework 2.0: основные принципы и некоторые особенности в библиотеке MSDN.

Теневой стек

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

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

К началу

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

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

К началу

Связанные разделы

Заголовок

Описание

Профилирование в .NET Framework 2.0

Описание изменений и улучшений в профилировании в платформе .NET Framework версии 2.0 и более поздних версий.

Установка профилирующей среды

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

Обратные вызовы загрузчика в интерфейсе API для профилирования

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

Сборка мусора в профилирующем API

Описание способов вызова, обнаружения и блокирования сборки мусора.

Отслеживание объектов в интерфейсе API для профилирования

Способы отслеживания объектов, которые были перемещены во время сборки мусора.

Проверка объектов в интерфейсе API для профилирования

Способы использования метаданных профилировщиком для получения сведений об объекте.

Обработка исключений в профилирующем API

Описание способов мониторинга событий исключения.

Создание кода в интерфейсе API для профилирования

Описание управления автоматическим и ручным созданием кода в профилировщике.

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

Описание класса, потока и идентификаторов доменов приложений, которые передаются средой CLR профилировщикам.

Соглашения методов интерфейса API для профилирования

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

Профилирующие интерфейсы

Описание неуправляемых интерфейсов, используемых API профилирования.

Глобальные статические функции профилирования

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

Перечисления профилирования

Описание неуправляемых перечислений, используемых API профилирования.

Структуры профилирования

Описание неуправляемых структур, используемых API профилирования.

К началу