Как написать профилировщик? - PullRequest
14 голосов
/ 15 декабря 2008

я хотел бы знать, как написать профилировщик? Какие книги и / или статьи рекомендуется? Может кто-нибудь помочь мне, пожалуйста?

Кто-то уже сделал что-то подобное?

Ответы [ 5 ]

11 голосов
/ 15 декабря 2008

Обнадеживающий лот, не правда ли:)

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

Так что, если вы просто хотите получить ответы, которые профилировщик даст вам, перейдите к тому, что написал кто-то другой. Если вы ищете интеллектуальный вызов, почему бы не попробовать его написать?

Я написал пару для сред выполнения, которые годы сделали неактуальными.

Есть два подхода

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

  • с таймером, регулярно отключающимся и просматривающим текущую программу.

Версия JVMPI кажется первого типа - ссылка, предоставленная uzhin, показывает, что она может сообщать о многих вещах (см. Раздел 1.3). То, что получает выполненные изменения, делает это, так что профилирование может повлиять на производительность (и если вы профилируете то, что в противном случае было очень легкой, но часто вызываемой функцией, это может ввести в заблуждение).

Если вы можете получить таймер / прерывание, сообщающее вам, где был программный счетчик во время прерывания, вы можете использовать таблицу символов / информацию отладки, чтобы определить, в какой функции это было в то время. Это дает меньше информации, но может быть менее разрушительным. Немного больше информации можно получить от обхода стека вызовов для определения абонентов и т. Д. Я не знаю, возможно ли это даже в Java ...

Paul.

8 голосов
/ 10 апреля 2009

Я написал один раз, главным образом как попытку сделать «глубокую выборку» более удобной для пользователя. Когда вы делаете метод вручную, это объясняется здесь . Он основан на выборке, но вместо того, чтобы брать большое количество маленьких выборок, вы берете небольшое количество больших выборок.

Например, он может сказать, что инструкция I (обычно это вызов функции) стоит вам более или менее процентов X общего времени выполнения, поскольку она появляется в стеке на X% выборок.

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

Сэмплер основан на предпосылке, что лучше знать адрес инструкции I с точностью (, потому что это то, что вы ищете ), чем знать число X% с точностью , Если вы знаете, что могли бы сэкономить примерно 30% времени путем перекодирования чего-либо, действительно ли вас волнует, что вы можете потерять 5%? Вы все еще хотите исправить это. Количество времени, которое оно фактически экономит, не будет больше или меньше уменьшено вашим знанием X.

Таким образом, есть возможность извлекать сэмплы из таймера, но, честно говоря, я обнаружил, что столь же полезно инициировать прерывание, когда пользователь нажимает обе клавиши Shift одновременно. Поскольку 20 сэмплов, как правило, достаточно, и таким образом вы можете быть уверены, что берут сэмплы в соответствующее время (т. Е. Не ожидая ввода данных пользователем), оно было вполне адекватным. Другой способ - делать выборки только по таймеру, пока пользователь удерживает обе клавиши Shift (или что-то в этом роде).

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

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

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

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

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

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

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

  • Как упоминалось ранее, скорость выборки не является проблемой, потому что мы не измеряем производительность, мы диагностируем. Выборка не влияет на результаты, потому что выборка не влияет на то, что делает программа в целом. Алгоритм, для выполнения которого требуется N инструкций, по-прежнему требует N инструкций, даже если его останавливали любое количество раз.

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

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

При обсуждении профилирования часто упускают из виду то, что вы можете сделать это несколько раз, чтобы найти несколько проблем. Например, предположим, что инструкция I1 находится в стеке 5% времени, а I2 находится в стеке 50% времени. Двадцать образцов легко найдут I2, но, возможно, не I1. Итак, вы исправите I2. Затем вы делаете все это снова, но теперь I1 занимает 10% времени, поэтому, вероятно, его увидят 20 образцов. Этот эффект увеличения позволяет многократно применять профилирование для достижения больших сложных коэффициентов ускорения.

8 голосов
/ 16 декабря 2008

Сначала я бы посмотрел на эти проекты с открытым исходным кодом:

Тогда я бы посмотрел на JVMTI (не JVMPI)

2 голосов
/ 15 декабря 2008

JVMPI spec: http://java.sun.com/j2se/1.5.0/docs/guide/jvmpi/jvmpi.html

Я приветствую вашу смелость и храбрость

РЕДАКТИРОВАТЬ: И, как отметил пользователь Boune, JVMTI: http://java.sun.com/developer/technicalArticles/Programming/jvmti/

0 голосов
/ 17 мая 2009

В качестве другого ответа я только что посмотрел на LukeStackwalker на sourceforge. Это хороший, маленький пример стекового сэмплера и хорошее место для начала, если вы хотите написать профилировщик.

Вот, на мой взгляд, то, что он делает правильно:

  • Сэмплирует весь стек вызовов.

Вздох ... так близко, пока так далеко. Вот что должен делать IMO (и другие сэмплеры стека, такие как xPerf):

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

  • Не нужно брать столько образцов, если проблема заключается в хранении. Поскольку типичные проблемы с производительностью стоят от 10% до 90%, 20-40 образцов покажут их достаточно надежно. Сотни образцов дают большую точность измерений, но они не увеличивают вероятность обнаружения проблем.

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

    5/20 MyFile.cpp: 326 для (i = 0; i

Это говорит о том, что строка 326 в MyFile.cpp обнаружена на 5 из 20 выборок в процессе вызова strlen. Это очень важно, потому что вы можете сразу увидеть проблему, и вы знаете, какое ускорение вы можете ожидать от ее устранения. Если вы замените strlen(s) на s[i], он больше не будет тратить время на этот вызов, поэтому эти выборки не будут выполняться, и ускорение составит приблизительно 1 / (1-5 / 20) = 20 / (20- 5) = 4/3 = 33% ускорения. (Спасибо Дэвиду Торнли за этот пример кода.)

  • Пользовательский интерфейс должен иметь представление «бабочка», показывающее операторы. (Если он также показывает функции, то все в порядке, но операторы действительно важны.) Например:

    3/20 MyFile.cpp: 502 MyFunction (myArgs)
    2/20 HisFile.cpp: 113 MyFunction (hisArgs)

    5/20 MyFile.cpp: 326 для (i = 0; i

    5/20 strlen.asm: 23 ... некоторый код сборки ...

В этом примере строка, содержащая оператор for, является «центром внимания». Это произошло на 5 образцах. Две строки над ним говорят, что в 3 из этих выборок он был вызван из MyFile.cpp:502, а в 2 из этих выборок он был вызван из HisFile.cpp:113. Строка под ним говорит, что на всех 5 из этих образцов она была в strlen (что неудивительно). В общем случае линия фокусировки будет иметь дерево «родителей» и дерево «детей». Если по какой-либо причине линию фокусировки не удается исправить, вы можете пойти вверх или вниз. Цель состоит в том, чтобы найти строки, которые вы можете исправить, на как можно большем количестве образцов.

ВАЖНО: Профилирование не должно рассматриваться как то, что вы делаете один раз . Например, в приведенном выше примере мы получили ускорение на 4/3, исправив одну строку кода. Когда процесс повторяется, другие проблемные строки кода должны отображаться с частотой 4/3, которую они делали раньше, и поэтому их легче найти. Я никогда не слышал, чтобы люди говорили об итерации процесса профилирования, но это крайне важно для получения общего большого ускорения.

P.S. Если в одном образце оператор встречается более одного раза, это означает, что имеет место рекурсия. Это не проблема. Он по-прежнему считается только одним образцом, содержащим утверждение. Все еще имеет место, что стоимость заявления аппроксимируется долей образцов, содержащих его.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...