Что такое профилирование в программировании
Перейти к содержимому

Что такое профилирование в программировании

  • автор:

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

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

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

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

JIT-компиляция во время выполнения обеспечивает прекрасные возможности для профилирования. API профилирования позволяет профилировщику вносить изменения потока кода MSIL в памяти для подпрограммы перед ее 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. Он просто устанавливает метки времени для каждого уведомления, собирает результаты и выводит список, в котором указывается, на какие функции было затрачено больше ресурсов ЦП или физического времени во время выполнения приложения.

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

Другими интерфейсами main, участвующими в профилировании, являются 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 может безопасно вызывать управляемый код, это текст MSIL в теле метода. Для изменения текста MSIL рекомендуется использовать методы JIT-перекомпиляции в интерфейсе ICorProfilerCallback4 .

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

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

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

    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 в этом наборе документации и Профилировщик стека Walk в платформа .NET Framework 2.0: Основные и более подробные сведения.

    Теневой стек

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

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

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

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

    См. также

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

    Совместная работа с нами на GitHub

    Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.

    Профилирование в программировании: какой профилировщик выбрать

    Lorem ipsum dolor

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

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

    Сегодня нас интересует, что такое профилировщик в программировании. Описание работы таких программ в других сферах оставим для следующих статей.

    Профилировщик в программировании — что это?

    • измерение времени, затраченного на ту или иную функцию;
    • измерение потраченных системных ресурсов на ту или иную функцию;
    • изменения программы в зависимости от воздействия на нее со стороны пользователей;
    • как запустилась и как прекратила работать программа;
    • были ли «зависания» в программе и из-за чего;
    • и др.

    Программы-профилировщики

    1. «gprof». Многоплатформенный и многофункциональный профилировщик.
    2. «VТune». Программный продукт компании Intel на платной основе.
    3. «Single Event API». Программный продукт компании Intel на бесплатной основе.
    4. «CodeAnalyst». Универсальный профилировщик компании AMD.
    5. «AQtime». Профилировщик для операционной системы Windows.
    6. «Instruments». Профилировщик для операционной системы MacOS.
    7. «Perf». Профилировщик для операционной системы Linux.
    8. «dotMemory». Профилировщик памяти разных систем.

    Заключение

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

    Мы будем очень благодарны

    если под понравившемся материалом Вы нажмёте одну из кнопок социальных сетей и поделитесь с друзьями.

    Профилирование (информатика)

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

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

    Это часто используется, чтобы определить, как долго выполняются определенные части программы, как часто они выполняются, или генерировать граф вызовов (Call Graph). Обычно эта информация используется, чтобы идентифицировать те участки программы, которые работают больше всего. Эти трудоёмкие участки могут быть оптимизированы, чтобы выполняться быстрее.

    Также выделяют анализ покрытия (Code Coverage) — процесс выявления неиспользуемых участков кода при помощи, например, многократного запуска программы.

    См. также

    Ссылки

    Профилирование программ

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

    Для начала рассмотрим профилирование фрагментов кода.

    В случае работы в Jupyter notebook можно использовать так называемые “магические команды”. Для того, чтобы узнать время выполнения одной строки, нужно в её начале разместить магическую команду %time. В приведенном ниже примере в последней ячейке тетрадки создается матрица размером 10000×10000, заполненная случайными вещественными числами

    Как видно, на это потребовалось 1,27 секунды. Отметим, что повторный запуск аналогичной команды потребовал уже 1,63 секунды.

    Чтобы оценить время выполнения кода в каждой из строк какой-либо ячейки, необходимо использовать магическую команду %time в каждой строке. В приведенном ниже примере создаются и перемножаются две матрицы размером 5000×5000, заполненные случайными вещественными числами.

    Если же нужно оценить время выполнения ячейки в целом, необходимо использовать команду %%time. Генерация двух матриц и их перемножение потребовали в сумме 4,7 секунды.

    Описанные приемы позволяют получить лишь начальное представление о быстродействии кода. Как известно, единичного эксперимента недостаточно для того, чтобы составить адекватное представление о поведении исследуемой системы. Проведем серию экспериментов и возьмем среднее значение в качестве оценки времени выполнения кода. Для этого будем использовать команду %%timeit с ключом -r, задающим количество вычислительных экспериментов.

    В следующем примере мы так же генерируем и перемножаем две матрицы в серии из пяти экспериментов.

    Как видно, среднее значение времени в серии экспериментов (5,37 секунды) отличается от времени единичного эксперимента, проведенного ранее (4,7 секунды).

    Рассмотрим еще один пример профилирования кода.

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

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

    Исследуем два способа вычисления приближенного значения синуса, отличающихся способом вычисления второго слагаемого: в одном случае через трехкратное перемножение аргумента, в другом — через операцию возведения в степень. Для измерения времени снова воспользуемся командой timeit.

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

    Перейдем к профилированию кода программы в целом.

    Представим, что есть программа min_distance_naive.py, вычисляющая наименьшее расстояние между точками на плоскости и началом координат. Координаты точек представлены матрицей размерности 1000000×2, записанной в файле points.npy. Ключевой фрагмент кода — функция min_dist_naive.

    import numpy as np def min_dist_naive(points, base): r_min = float('inf') for p in points: r = ((p[0] - base[0]) ** 2 + (p[1] - base[1]) ** 2) ** (1 / 2) r_min = min(r, r_min) return r_min points = np.load('points.npy') origin = (0, 0) min_dist = min_dist_naive(points, origin) print(min_dist)

    Для запуска этой программы необходимо в командной строке выполнить следующую команду:

    python min_distance_naive.py

    Для запуска профилирования дополним эту команду ключом -m cProfile указывающим, что для профилирования нужно использовать модуль cPython, и ключом -s time, указывающим, что результаты профилирования нужно упорядочить по времени.

    python -m cPython -s time min_distance_naive.py > naive.txt

    Весь вывод, в том числе результаты профилирования, будут записаны в текстовый файл naive.txt. Анализ этого файла показывает, что время выполнения программы составило 4,369 с., суммарное время выполнения функции поиска минимального расстояния составило 4,132 с.

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

    import numpy as np def min_dist_optim(points, base): r_min = float('inf') for p in points: r = (p[0] - base[0]) ** 2 + (p[1] - base[1]) ** 2 r_min = min(r, r_min) r_min = r_min ** (1 / 2) return r_min points = np.load('points.npy') origin = (0, 0) min_dist = min_dist_optim(points, origin) print(min_dist)

    Запустим профилирование доработанной программы.

    python -m cPython -s time min_distance_optimized.py > optimized.txt

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

    В этом случае время выполнения функции составило 3,772 с.

    По результатам многократного профилирования обоих вариантов и статистической обработки полученных экспериментальных данных, включающей в том числе сравнение выборок с помощью t-критерия Уэлча, получен статистически значимый результат, свидетельствующий о том, что модифицированная функция приблизительно на 8% быстрее исходной. Таким образом, профилирование помогает выявить узкие места в коде с точки зрения производительности, сравнить разные варианты реализации алгоритмов, и в конечном счете ускорить выполнение программ. Код приведенных примеров, а также экспериментальные данные можно найти в репозитории.

    • профилирование
    • программ
    • Python
    • Программирование
    • Машинное обучение

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *