Как написать профилировщик?

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

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

15.12.2008 16:02:47
5 ОТВЕТОВ
РЕШЕНИЕ

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

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

8
16.12.2008 00:20:04

Спецификация JVMPI: http://java.sun.com/j2se/1.5.0/docs/guide/jvmpi/jvmpi.html

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

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

2
16.12.2008 17:38:28

Обнадеживает много, не правда ли :)

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

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

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

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

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

  • иметь таймер, регулярно уходящий и заглядывающий, где программа в настоящее время.

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

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

Павел.

11
15.12.2008 16:45:21

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

Например, он может сказать вам, что инструкция 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
23.05.2017 10:27:42
Это звучит как отличный подход для поиска функции узкого места в программе. Но я не вижу необходимости избавляться от необходимости в традиционном профилировщике, когда вы выполняете низкоуровневую настройку. Вам нужны точные измерения для подтверждения ваших изменений. Если я Fooпринимал 50% до моего изменения и 40% после, сделал ли я значительное улучшение или это ошибка выборки? И иногда вам нужно прибить узкое место к уровню ветки if, которая срабатывает гораздо чаще, чем вы ожидаете. Если это встроенный код, то стек вызовов не даст вам этого.
Adrian McCarthy 22.04.2010 23:50:18
@Adrian McCarthy: (Я не буду касаться встраивания, потому что вы всегда можете перейти к машинному коду и вернуться к исходному тексту.) Что касается вашего 50-40% балла, в этой парадигме вы не смотрите на функции и ожидаете небольших изменений как это. Вместо этого вы смотрите на строки (или инструкции, звоните или нет) и видите, какой процент времени они составляют и (что крайне важно) предугадывают, почему это время тратится. ...
Mike Dunlavey 23.04.2010 01:40:03
@Adrian McCarthy: ... Если это не очень хорошее «почему», то есть вы можете найти способ не тратить его, то% на этой строке не просто немного падает, он часто исчезает, и 1) все время уменьшается на этот%, и 2) распределение того, что утверждения занимают временные сдвиги. (Нет ничего плохого в измерениях с помощью традиционного профилировщика, но мне это никогда не нужно, потому что общее время уменьшается на процент, за который отвечала строка или инструкция.)
Mike Dunlavey 23.04.2010 01:43:28
@Adrian McCarthy: Не для того, чтобы понять, но, возможно, вы можете видеть, как точность измерения никогда не должна входить в процесс, потому что если строка кода (или целая функция) находится в стеке на 50% и оптимизирована до 40%, то это легко увидеть, потому что эти 10% приходятся непосредственно на общее время, где простой секундомер может их измерить без каких-либо сомнений в точности.
Mike Dunlavey 23.04.2010 01:55:50

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

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

  • Это образец всего стека вызовов.

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

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

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

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

    5/20 MyFile.cpp: 326 для (i = 0; i <strlen (s); ++ 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 <strlen (s); ++ i)

    5/20 strlen.asm: 23 ... немного кода сборки ...

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

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

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

0
17.05.2009 16:57:42