Если ваша цель - использовать профилировщик, воспользуйтесь одним из предложенных.
Однако, если вы спешите и можете вручную прервать программу под отладчиком, пока она субъективно медленная, существует простой способ найти проблемы с производительностью.
Просто остановите его несколько раз и каждый раз смотрите на стек вызовов. Если есть какой-то код, который тратит впустую некоторый процент времени, 20% или 50% или что-то еще, то есть вероятность, что вы поймаете его в действии на каждом образце. Так что это примерно процент образцов, на которых вы это увидите. Там не требуется образованное предположение.
Если у вас есть предположение, в чем заключается проблема, это докажет или опровергнет ее.
У вас могут быть проблемы с производительностью разных размеров. Если вы уберете какой-либо из них, остальные будут занимать больший процент, и его будет легче обнаружить при последующих проходах.
Этот эффект увеличения в сочетании с несколькими проблемами может привести к действительно огромным факторам ускорения.
Предостережение: программисты склонны скептически относиться к этой технике, если они сами не использовали ее. Они скажут, что профилировщики предоставляют вам эту информацию, но это верно только в том случае, если они производят выборку всего стека вызовов, а затем позволяют исследовать случайный набор выборок. (Сводные данные - то, где понимание потеряно.) Графики вызовов не дают вам ту же информацию, потому что
- они не суммируются на уровне инструкций, а
- они дают запутанные выводы при наличии рекурсии.
Они также скажут, что это работает только на игрушечных программах, когда на самом деле это работает на любой программе, и, кажется, работает лучше на больших программах, потому что у них, как правило, больше проблем для поиска.
Они скажут, что иногда он находит вещи, которые не являются проблемами, но это верно только в том случае, если вы видите что-то один раз . Если вы видите проблему более чем на одном образце, это реально.
P.S. Это также может быть сделано в многопоточных программах, если есть способ собрать выборки стека вызовов из пула потоков в определенный момент времени, как в Java.
P.P.S Как грубое обобщение, чем больше уровней абстракции в вашем программном обеспечении, тем больше вероятность того, что вы обнаружите, что это является причиной проблем с производительностью (и возможностью получить ускорение).
Добавлено: Это может быть неочевидно, но техника выборки из стека работает одинаково хорошо при наличии рекурсии. Причина заключается в том, что время, которое будет сэкономлено удалением инструкции, аппроксимируется долей содержащих ее выборок, независимо от того, сколько раз это может произойти в выборке.
Другое возражение, которое я часто слышу, звучит так: « Это остановит где-то случайно и пропустит реальную проблему ».
Это происходит из-за наличия предварительного представления о том, что является реальной проблемой.
Ключевым свойством проблем с производительностью является то, что они не поддаются ожиданиям.
Выборка говорит вам, что что-то является проблемой, и ваша первая реакция - неверие.
Это естественно, но вы можете быть уверены, что если оно обнаружит проблему, то это реально, и наоборот.
ДОБАВЛЕНО: Позвольте мне сделать байесовское объяснение того, как это работает. Предположим, что есть какая-то инструкция I
(вызов или другое), которая находится в стеке вызовов в некоторой доле f
времени (и, следовательно, стоит так дорого). Для простоты предположим, что мы не знаем, что такое f
, но предположим, что это либо 0,1, 0,2, 0,3, ... 0,9, 1,0, и априорная вероятность каждой из этих возможностей равна 0,1, поэтому все эти затраты одинаково вероятны априори.
Тогда предположим, что мы берем только 2 выборки из стека и видим инструкцию I
для обеих выборок, обозначенную как наблюдение o=2/2
. Это дает нам новые оценки частоты f
I
, согласно этому:
Prior
P(f=x) x P(o=2/2|f=x) P(o=2/2&&f=x) P(o=2/2&&f >= x) P(f >= x | o=2/2)
0.1 1 1 0.1 0.1 0.25974026
0.1 0.9 0.81 0.081 0.181 0.47012987
0.1 0.8 0.64 0.064 0.245 0.636363636
0.1 0.7 0.49 0.049 0.294 0.763636364
0.1 0.6 0.36 0.036 0.33 0.857142857
0.1 0.5 0.25 0.025 0.355 0.922077922
0.1 0.4 0.16 0.016 0.371 0.963636364
0.1 0.3 0.09 0.009 0.38 0.987012987
0.1 0.2 0.04 0.004 0.384 0.997402597
0.1 0.1 0.01 0.001 0.385 1
P(o=2/2) 0.385
В последнем столбце говорится, что, например, вероятность того, что f
> = 0,5, составляет 92%, по сравнению с предыдущим предположением 60%.
Предположим, что предыдущие предположения отличаются. Предположим, мы предполагаем, что P (f = 0,1) составляет 0,991 (почти наверняка), а все остальные возможности практически невозможны (0,001). Другими словами, наша предварительная уверенность в том, что I
дешево. Тогда мы получим:
Prior
P(f=x) x P(o=2/2|f=x) P(o=2/2&& f=x) P(o=2/2&&f >= x) P(f >= x | o=2/2)
0.001 1 1 0.001 0.001 0.072727273
0.001 0.9 0.81 0.00081 0.00181 0.131636364
0.001 0.8 0.64 0.00064 0.00245 0.178181818
0.001 0.7 0.49 0.00049 0.00294 0.213818182
0.001 0.6 0.36 0.00036 0.0033 0.24
0.001 0.5 0.25 0.00025 0.00355 0.258181818
0.001 0.4 0.16 0.00016 0.00371 0.269818182
0.001 0.3 0.09 0.00009 0.0038 0.276363636
0.001 0.2 0.04 0.00004 0.00384 0.279272727
0.991 0.1 0.01 0.00991 0.01375 1
P(o=2/2) 0.01375
Теперь говорится, что P (f> = 0,5) составляет 26%, по сравнению с предыдущим предположением 0,6%. Таким образом, Байес позволяет нам обновить нашу оценку вероятной стоимости I
. Если объем данных невелик, он точно не говорит нам о стоимости, а лишь о том, что он достаточно велик, чтобы его можно было исправить.
Еще один способ взглянуть на него называется Правило наследования .
Если вы подбрасываете монету 2 раза, и она выпадает в голову оба раза, что это говорит вам о вероятном весе монеты?
Уважаемый способ ответить на этот вопрос - сказать, что это бета-распределение со средним значением (количество попаданий + 1) / (количество попыток + 2) = (2 + 1) / (2 + 2) = 75%.
(Ключ в том, что мы видим I
более одного раза. Если мы видим его только один раз, это нам мало что говорит, кроме того, что f
> 0.)
Таким образом, даже очень небольшое количество образцов может многое рассказать нам о стоимости инструкций, которые он видит. (И он будет видеть их с частотой, в среднем, пропорциональной их стоимости. Если взято n
образцов, а f
- это стоимость, то I
появится на nf+/-sqrt(nf(1-f))
образцах. Пример, n=10
, f=0.3
, то есть 3+/-1.4
образцов.)
ДОБАВЛЕНО, чтобы дать интуитивное представление о разнице между измерением и выборкой из случайного стека:
Сейчас есть профилировщики, которые производят выборку стека, даже по времени настенных часов, но , что выходит , это измерения (или «горячая линия», или «горячая точка», от которой «узкое место» может легко скрыться). То, что они вам не показывают (и они легко могут), - это сами образцы. И если ваша цель состоит в том, чтобы найти узкое место, то количество их, которое вам нужно увидеть, составляет в среднем , 2, деленное на долю времени, которое требуется.
Таким образом, если это займет 30% времени, 2 / .3 = 6,7 выборки в среднем покажет это, и вероятность того, что 20 выборок покажет это, составляет 99,2%.
Вот нерегулярная иллюстрация разницы между исследованием измерений и исследованием образцов стеков.
Узким местом может быть один такой большой шарик или множество маленьких, это не имеет значения.
Измерение горизонтальное; он говорит вам, какую часть времени занимают определенные подпрограммы.
Выборка вертикальная.
Если есть какой-либо способ избежать того, что вся программа делает в этот момент, , и если вы видите это во втором примере , вы нашли узкое место.
Вот в чем разница - видя всю причину затраченного времени, а не только сколько.