[Resolved] Как я могу профилировать код C ++, работающий в Linux?

Вопрос:

У меня есть приложение C ++, работающее в Linux, которое я оптимизирую.

Как я могу определить, какие области моего кода работают медленно?

Отвечать:

Если ваша цель – использовать профилировщик, воспользуйтесь одним из предложенных.

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

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

Итак, это примерно процент образцов, на которых вы это увидите. Не требуется никаких обоснованных догадок. Если у вас есть предположение, в чем проблема, это докажет или опровергнет ее.

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

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

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

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

Графики звонков не дают той же информации, потому что

  • Они не резюмируют на уровне инструкций, и
  • Они дают запутанные сводки при наличии рекурсии.

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

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

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

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

Добавлен: Это может быть неочевидно, но метод выборки стека одинаково хорошо работает и при наличии рекурсии.

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

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

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

Это естественно, но вы можете быть уверены, что если он обнаружит проблему, она реальна, и наоборот.

Добавлен: Позвольте мне дать байесовское объяснение того, как это работает. Допустим, есть какая-то инструкция 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) / (number of tries + 2) = (2+1)/(2+2) = 75%.

(Ключ в том, что мы видим меня более одного раза. Если мы увидим это только один раз, это мало что скажет нам, кроме того, что f > 0.)

Таким образом, даже очень небольшое количество образцов может многое сказать нам о стоимости инструкций, которые он видит. (И он будет видеть их в среднем с частотой, пропорциональной их стоимости.

Если n взяты образцы, и f стоимость, тогда I появится на nf+/-sqrt(nf(1-f)) образцы. Пример, n=10, f = 0,3, то есть 3+/-1.4 образцы.)

Добавлено: Чтобы дать интуитивное представление о разнице между измерением и случайной выборкой стека:

Сейчас есть профилировщики, которые производят замеры стека, даже по настенным часам, но в результате получаются измерения (или горячая линия, или горячая точка, от которой «узкое место» может легко спрятаться).

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

Таким образом, если это занимает 30% времени, в среднем 2 / 0,3 = 6,7 выборки покажут это, а вероятность того, что 20 образцов покажут это, составляет 99,2%.

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

Измерение горизонтальное; он сообщает вам, сколько времени занимает выполнение определенных процедур.

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

Вот в чем разница – видеть всю причину потраченного времени, а не только то, сколько.

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

valgrind --tool=callgrind ./(Your binary)

Он сгенерирует файл с именем callgrind.out.x. Затем вы можете использовать kcachegrind инструмент для чтения этого файла. Это даст вам графический анализ вещей с результатами, например, сколько строк стоит.

Примечание: Примечание: valgrind замечательно, но имейте в виду, что это сделает вашу программу чертовски медленной.

Похожие записи

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

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