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

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

17.12.2008 20:29:24
Если вы предоставите больше данных о вашем стеке разработки, вы можете получить лучшие ответы. Есть профилировщики от Intel и Sun, но вы должны использовать их компиляторы. Это вариант?
Nazgob 17.12.2008 20:38:17
На него уже ответили по следующей ссылке: stackoverflow.com/questions/2497211/…
Kapil Gupta 22.05.2012 10:12:22
Большинство ответов - codeпрофилировщики. Однако инверсия приоритетов, наложение псевдонимов в кэше, конфликт ресурсов и т. Д. Могут быть факторами оптимизации и производительности. Я думаю, что люди читают информацию в мой медленный код . Часто задаваемые вопросы ссылаются на эту тему.
artless noise 17.03.2013 18:44:47
Раньше я использовал pstack случайным образом, большую часть времени распечатывает наиболее типичный стек, где программа находится большую часть времени, следовательно, указывая на узкое место.
Jose Manuel Gomez Alvarez 15.12.2016 09:26:00
19 ОТВЕТОВ
РЕШЕНИЕ

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

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

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

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

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

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

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

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) / (количество попыток + 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%.

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

введите описание изображения здесь

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

1397
26.02.2019 01:11:39
Это в основном профилировщик выборки для бедного человека, и это здорово, но вы рискуете получить слишком маленький размер выборки, который, возможно, даст вам совершенно ложные результаты.
Crashworks 22.05.2009 21:56:36
@Crash: Я не буду обсуждать часть «бедняк» :-) Это правда, что точность статистического измерения требует много выборок, но есть две противоречивые цели - измерение и проблемное местоположение. Я сосредотачиваюсь на последнем, для которого вам нужна точность определения местоположения, а не точность измерения. Так, например, в середине стека может быть один вызов функции A (); это составляет 50% времени, но это может быть в другой большой функции B, наряду со многими другими вызовами A (), которые не являются дорогостоящими. Точное суммирование времени функционирования может быть подсказкой, но любой другой пример стека будет точно определять проблему.
Mike Dunlavey 23.05.2009 01:14:27
... мир, кажется, считает, что граф вызовов, аннотированный счетчиком вызовов и / или средним временем, достаточно хорош. Не то. И грустная часть в том, что для тех, кто выбирает стек вызовов, наиболее полезная информация находится прямо перед ними, но они отбрасывают ее в интересах «статистики».
Mike Dunlavey 24.05.2009 18:08:35
Я не хочу не соглашаться с вашей техникой. Понятно, что я очень сильно полагаюсь на профилировщики сэмплирования. Я просто отмечаю, что есть некоторые инструменты, которые делают это в автоматическом режиме, что важно, когда вы достигли точки получения функции с 25% до 15% и вам нужно снизить ее с 1,2% до 0,6%.
Crashworks 2.06.2009 03:27:19
-1: Идеальная идея, но если вам платят за работу даже в среде с умеренной производительностью, это пустая трата времени каждого. Используйте настоящий профилировщик, чтобы нам не приходилось идти за вами и исправлять проблемы.
Sam Harwell 8.04.2010 13:26:32

Я полагаю, вы используете GCC. Стандартным решением будет профилирование с помощью gprof .

Не забудьте добавить -pgв компиляцию перед профилированием:

cc -o myprog myprog.c utils.c -g -pg

Я еще не пробовал, но слышал хорошие вещи о google-perftools . Это определенно стоит попробовать.

Связанный вопрос здесь .

Несколько других модных слов, если вы gprofэтого не сделаете: Valgrind , Intel VTune , Sun DTrace .

346
12.08.2017 17:35:44
Я согласен, что gprof является текущим стандартом. Просто отметим, что Valgrind используется для профилирования утечек памяти и других связанных с памятью аспектов ваших программ, а не для оптимизации скорости.
Bill the Lizard 18.12.2008 15:02:11
Билл, в vaglrind suite вы можете найти callgrind и массив. Оба очень полезны для профилирования приложений
dario minonne 18.12.2008 15:05:55
@ Bill-the-Lizard: Некоторые комментарии к gprof : stackoverflow.com/questions/1777556/alternatives-to-gprof/…
Mike Dunlavey 4.03.2010 13:23:53
gprof -pg - это только приблизительное значение профилирования стека вызовов. Он вставляет вызовы mcount для отслеживания того, какие функции вызывают какие другие функции. Он использует стандартную временную выборку для времени. Затем он распределяет время выборки в функции foo () обратно вызывающим функциям foo (), пропорционально количеству вызовов. Так что он не различает звонки с разными затратами.
Krazy Glew 28.04.2012 05:45:56
С помощью clang / clang ++ можно рассмотреть возможность использования профилировщика ЦП gperftools . Будьте осторожны.
einpoklum 14.10.2019 14:27:03

Вы можете использовать Valgrind со следующими опциями

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

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

575
5.09.2015 10:25:42
valgrind великолепен, но
neves 25.01.2012 20:07:02
Проверьте также Gprof2Dot для удивительного альтернативного способа визуализации вывода. ./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
Sebastian 22.05.2013 13:42:48
@neves Да Valgrind просто не очень помогает с точки зрения скорости для профилирования приложений "gstreamer" и "opencv" в реальном времени.
enthusiasticgeek 22.05.2013 20:20:04
stackoverflow.com/questions/375913/… является частичным решением проблемы скорости.
Tõnu Samuel 9.07.2014 10:33:01
@Sebastian: gprof2dotсейчас здесь: github.com/jrfonseca/gprof2dot
John Zwinck 27.04.2017 03:19:13

Я бы использовал Valgrind и Callgrind в качестве основы для своего набора инструментов профилирования. Важно знать, что Valgrind - это виртуальная машина:

(википедия) Valgrind по сути является виртуальной машиной, использующей методы JIT-компиляции "точно в срок", включая динамическую перекомпиляцию. Ничто из оригинальной программы никогда не запускается непосредственно на главном процессоре. Вместо этого Valgrind сначала переводит программу во временную, более простую форму, называемую промежуточным представлением (IR), которая является независимой от процессора формой на основе SSA. После преобразования инструмент (см. Ниже) может выполнять любые преобразования, которые он хотел бы, на IR, прежде чем Valgrind переведет IR обратно в машинный код и позволит хост-процессору запустить его.

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

Другой инструмент, основанный на Вальгринде, - Массив. Я использую его для профилирования использования памяти кучи. Работает отлично. Что он делает, так это то, что он дает вам снимки использования памяти - детальная информация, ЧТО содержит какой процент памяти, и ВОЗ поместила его туда. Такая информация доступна в разные моменты времени запуска приложения.

74
22.05.2009 21:44:19

Более новые ядра (например, новейшие ядра Ubuntu) поставляются с новыми инструментами 'perf' ( apt-get install linux-tools) AKA perf_events .

Они идут с классическими профилировщиками сэмплирования ( man-page ), а также с отличным графиком !

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

Альтернативный текст

256
12.08.2017 17:30:25
Отличный инструмент! Можно ли как-нибудь получить типичный вид "бабочки", который начинается со стиля "main-> func1-> fun2"? Я не могу понять это ... perf reportкажется, дает мне имена функций с родителями вызова ... (так что это вид перевернутой бабочки)
kizzx2 1.10.2010 06:17:51
Уилл, может перфект показать график активности потока; с добавленной информацией о количестве процессоров? Я хочу видеть, когда и какой поток работал на каждом процессоре.
osgx 6.12.2011 04:24:17
@ kizzx2 - вы можете использовать gprof2dotи perf script. Очень хороший инструмент!
dashesy 14.05.2012 23:55:46
Даже более новые ядра, такие как 4.13, имеют eBPF для профилирования. См. Brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html и brendangregg.com/ebpf.html
Andrew Stern 13.10.2017 15:00:16
Еще одно приятное введение perfсуществует в archive.li/9r927#selection-767.126-767.271 (Почему боги SO решили удалить эту страницу из базы знаний SO, вне моего
ragerdl 28.06.2018 17:20:11

Это ответ на ответ Назгоба от Gprof .

Я использовал Gprof последние пару дней и уже нашел три существенных ограничения, одно из которых я не видел нигде (пока), документированных:

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

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

  3. Он говорит здесь , что «... цифры ЧИСЛО-вызовов получены путем подсчета, не пробуя. Они абсолютно точны ...». Тем не менее, я нахожу свой график вызовов, показывающий мне 5345859132 + 784984078 в качестве статистики вызовов для моей наиболее вызываемой функции, где первый номер должен быть прямым вызовом, а второй рекурсивный вызов (который все из себя). Поскольку это означало, что у меня была ошибка, я вставил в код длинные (64-битные) счетчики и повторил тот же прогон. Мои счета: 5345859132 прямых и 78094395406 саморекурсивных вызовов. Там много цифр, поэтому я укажу, что измеряемые мной рекурсивные вызовы составляют 78 млрд. Против 784 млн. Из Gprof: в 100 раз больше. Оба запуска были однопотоковым и неоптимизированным кодом, один скомпилированный, -gа другой -pg.

Это был GNU Gprof (GNU Binutils для Debian) 2.18.0.20080103, работающий под 64-битным Debian Lenny, если это кому-нибудь поможет.

58
15.09.2018 09:33:33
Да, это делает выборку, но не для числа звонков. Интересно, что следование по вашей ссылке в конечном итоге привело меня к обновленной версии страницы руководства, на которую я ссылался в моем сообщении, по новому URL: sourceware.org/binutils/docs/gprof/… Это повторяет цитату в части (iii) моего ответа: но также говорит: «В многопоточных приложениях или однопоточных приложениях, которые связаны с многопоточными библиотеками, счетчик является только детерминированным, если функция подсчета является поточно-ориентированной. (Примечание: имейте в виду, что функция подсчета mcount в glibc не является поточной -безопасно)."
Rob_before_edits 22.06.2012 04:30:03
Мне не ясно, объясняет ли это мой результат в (iii). Мой код был связан с -lpthread -lm и объявил как статическую переменную «pthread_t * thr», так и «pthread_mutex_t nextLock = PTHREAD_MUTEX_INITIALIZER», даже когда он выполнялся однопоточным. Я бы обычно предполагал, что «связь с многопоточными библиотеками» означает на самом деле использование этих библиотек, и в большей степени, чем это, но я могу ошибаться!
Rob_before_edits 22.06.2012 06:05:28

Ответ на запуск valgrind --tool=callgrindне совсем полный без некоторых вариантов. Мы обычно не хотим профилировать 10 минут медленного запуска в Valgrind и хотим профилировать нашу программу, когда она выполняет какую-то задачу.

Так что это то, что я рекомендую. Сначала запустите программу:

valgrind --tool=callgrind --dump-instr=yes -v --instr-atstart=no ./binary > tmp

Теперь, когда это работает, и мы хотим начать профилирование, мы должны запустить в другом окне:

callgrind_control -i on

Это включает профилирование. Чтобы выключить и остановить всю задачу, мы можем использовать:

callgrind_control -k

Теперь у нас есть несколько файлов с именем callgrind.out. * В текущем каталоге. Чтобы увидеть результаты профилирования, используйте:

kcachegrind callgrind.out.*

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

69
5.09.2015 10:44:39
Теперь по какой-то причине файлы callgrind.out. * Всегда были пустыми. Выполнение callgrind_control -d было полезно для принудительного сброса данных на диск.
Tõnu Samuel 31.07.2014 04:25:47
Не могу. Мои обычные контексты - это что-то вроде целого MySQL или PHP или что-то подобное. Часто даже не знаю, что я хочу отделить сначала.
Tõnu Samuel 21.11.2015 22:50:29
Или в моем случае моя программа фактически загружает кучу данных в кэш LRU, и я не хочу это профилировать. Поэтому при запуске я принудительно загружаю подмножество кэша и профилирую код, используя только эти данные (что позволяет процессору OS + управлять использованием памяти в моем кэше). Это работает, но загрузка этого кеша медленная и интенсивно загружает весь код, который я пытаюсь профилировать в другом контексте, поэтому callgrind дает сильно загрязненные результаты.
Code Abominator 17.03.2016 03:49:42
также есть CALLGRIND_TOGGLE_COLLECTвозможность включать / отключать сбор программно; см. stackoverflow.com/a/13700817/288875
Andre Holzner 29.08.2017 16:59:43
Вау, я не знал, что это было, спасибо!
Vincent Fourmond 3.11.2019 19:39:56

Вот два метода, которые я использую для ускорения моего кода:

Для приложений, связанных с процессором:

  1. Используйте профилировщик в режиме отладки для определения сомнительных частей вашего кода
  2. Затем переключитесь в режим RELEASE и закомментируйте сомнительные разделы вашего кода (заглушите его ничем), пока не увидите изменения в производительности.

Для приложений ввода-вывода:

  1. Используйте профилировщик в режиме RELEASE для определения сомнительных частей вашего кода.

NB

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

Для CPU, причина для профилирования в режиме DEBUG заключается в том, что если вы попробовали профилирование в режиме RELEASE , компилятор собирается уменьшить математические, векторизованные циклы и встроенные функции, которые имеют тенденцию превращать ваш код в не отображаемый беспорядок при сборке. Непоправимый беспорядок означает, что ваш профилировщик не сможет четко определить, что занимает так много времени, поскольку сборка может не соответствовать оптимизируемому исходному коду . Если вам нужна производительность (например, чувствительная к времени) в режиме RELEASE , отключите функции отладчика, чтобы сохранить работоспособность.

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

3
5.09.2015 23:35:51
+1 Метод бедняка работает так же хорошо для привязки ввода / вывода, как и для привязки к процессору, и я рекомендую выполнять всю настройку производительности в режиме отладки. Когда вы закончите настройку, включите RELEASE. Это улучшит ситуацию, если программа в вашем коде связана с процессором. Вот грубое, но короткое видео процесса.
Mike Dunlavey 27.06.2014 20:55:26
Я бы не использовал сборки DEBUG для профилирования производительности. Часто я видел, что критические части производительности в режиме DEBUG полностью оптимизируются в режиме выпуска. Другая проблема - использование утверждений в отладочном коде, которые добавляют шум производительности.
gast128 21.07.2014 18:55:57
Ты вообще прочитал мой пост? «Если вам нужна производительность (например, чувствительная ко времени) в режиме RELEASE, отключите функции отладчика, чтобы сохранить работоспособность», «Затем переключитесь в режим RELEASE и комментируйте сомнительные разделы вашего кода (заглушайте его ничем), пока не увидите изменения в производительности. " Я сказал проверить возможные проблемные области в режиме отладки и проверить эти проблемы в режиме выпуска, чтобы избежать ошибки, которую вы упомянули.
seo 22.07.2014 15:54:03

Используйте Valgrind, callgrind и kcachegrind:

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

генерирует callgrind.out.x. Прочитайте это, используя kcachegrind.

Используйте gprof (добавьте -pg):

cc -o myprog myprog.c utils.c -g -pg 

(не очень хорошо для многопоточности, указателей на функции)

Используйте google-perftools:

Использует временную выборку, выявляются узкие места ввода-вывода и ЦП.

Intel VTune является лучшим (бесплатно для образовательных целей).

Другие: AMD Codeanalyst (с заменой на AMD CodeXL), OProfile, инструменты 'perf' (apt-get install linux-tools)

23
6.09.2018 16:45:37

Для однопоточных программ вы можете использовать igprof , Ignominous Profiler: https://igprof.org/ .

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

5
17.03.2018 12:20:45
Это выглядит интересно, но не компилируется с GCC 9.2. (Debian / Sid) Я сделал проблему на github.
Basile Starynkevitch 11.01.2020 21:47:51

Также стоит упомянуть

  1. HPCToolkit ( http://hpctoolkit.org/ ) - с открытым исходным кодом, работает для параллельных программ и имеет графический интерфейс для просмотра результатов несколькими способами
  2. Intel VTune ( https://software.intel.com/en-us/vtune ) - если у вас есть компиляторы Intel, это очень хорошо
  3. TAU ( http://www.cs.uoregon.edu/research/tau/home.php )

Я использовал HPCToolkit и VTune, и они очень эффективны при поиске длинного полюса в палатке и не требуют перекомпиляции вашего кода (за исключением того, что вы должны использовать -g -O или сборку типа RelWithDebInfo в CMake для получения значимого вывода) , Я слышал, что TAU схожи по возможностям.

3
14.09.2018 22:56:48

Вы можете использовать библиотеку iprof:

https://gitlab.com/Neurochrom/iprof

https://github.com/Neurochrom/iprof

Он кроссплатформенный и позволяет не измерять производительность вашего приложения также в режиме реального времени. Вы можете даже соединить это с живым графиком. Полный отказ от ответственности: я автор.

2
24.02.2019 18:01:00

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

Он находится на C ++ и должен быть настроен в соответствии с вашими потребностями. К сожалению, я не могу поделиться кодом, только понятиями. Вы используете «большой» volatileбуфер, содержащий метки времени и идентификатор события, который вы можете выгрузить после вскрытия или после остановки системы ведения журнала (и, например, выгрузить ее в файл).

Вы получаете так называемый большой буфер со всеми данными, а небольшой интерфейс анализирует его и показывает события с именем (вверх / вниз + значение), как осциллограф с цветами (настраивается в .hppфайле).

Вы настраиваете количество генерируемых событий, чтобы сосредоточиться исключительно на том, что вы хотите. Это очень помогло нам в планировании проблем при использовании требуемого количества ЦП в зависимости от количества зарегистрированных событий в секунду.

Вам нужно 3 файла:

toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID

Концепция состоит в том, чтобы определять события tool_events_id.hppследующим образом:

// EVENT_NAME                         ID      BEGIN_END BG_COLOR NAME
#define SOCK_PDU_RECV_D               0x0301  //@D00301 BGEEAAAA # TX_PDU_Recv
#define SOCK_PDU_RECV_F               0x0302  //@F00301 BGEEAAAA # TX_PDU_Recv

Вы также определяете несколько функций в toolname.hpp:

#define LOG_LEVEL_ERROR 0
#define LOG_LEVEL_WARN 1
// ...

void init(void);
void probe(id,payload);
// etc

Везде, где в вашем коде вы можете использовать:

toolname<LOG_LEVEL>::log(EVENT_NAME,VALUE);

probeФункция использует несколько сборочных линий , чтобы получить тактовую метку времени ASAP , а затем устанавливает запись в буфер. У нас также есть атомарный инкремент, чтобы безопасно найти индекс, где хранить событие журнала. Конечно, буфер является круглым.

Надеюсь, что идея не запутана отсутствием примера кода.

1
17.05.2019 10:13:01

Вы можете использовать каркас журналирования, например, loguruтак как он включает метки времени и общее время безотказной работы, которые можно использовать для профилирования:

1
21.05.2019 13:28:31

Поскольку никто не упомянул Arm MAP, я бы добавил, что лично я успешно использовал Map для профилирования научной программы на C ++.

Arm MAP - это профилировщик для параллельных, многопоточных или однопоточных кодов C, C ++, Fortran и F90. Он обеспечивает углубленный анализ и точное определение узкого места в исходной строке. В отличие от большинства профилировщиков, он предназначен для профилирования pthreads, OpenMP или MPI для параллельного и многопоточного кода.

MAP это коммерческое программное обеспечение.

0
28.06.2019 04:44:24

На самом деле немного удивлен, что многие не упоминали о google / benchmark , хотя немного сложно связать определенную область кода, особенно если база кода немного большая, однако я нашел это действительно полезным при использовании в сочетании сcallgrind

ИМХО, идентификация части, которая вызывает узкое место, является ключом здесь. Однако сначала я постараюсь ответить на следующие вопросы и выбрать инструмент, основанный на этом.

  1. мой алгоритм правильный?
  2. есть ли замки, которые оказываются узкими местами?
  3. Есть ли определенный раздел кода, который оказывается виновником?
  4. как насчет ввода-вывода, обработки и оптимизации?

valgrindс комбинацией callrindи kcachegrindдолжен обеспечить достойную оценку по пунктам выше, и как только будет установлено, что есть проблемы с каким-то разделом кода, я бы посоветовал сделать микропробную отметку google benchmarkхорошим началом.

1
3.11.2019 14:47:54

Используйте -pgфлаг при компиляции и компоновке кода и запустите исполняемый файл. Во время выполнения этой программы данные профилирования собираются в файле a.out.
Существует два разных типа профилирования

1 - Плоское профилирование:
запустив команду, gprog --flat-profile a.outвы получите следующие данные
- какой процент от общего времени, потраченного на функцию,
- сколько секунд было потрачено на функцию, включая и исключая вызовы подфункций,
- количество звонки,
- среднее время звонка.

2- представьте
нам команду gprof --graph a.outдля получения следующих данных для каждой функции, которая включает:
- В каждом разделе одна функция помечена индексным номером.
- Выше функции есть список функций, которые вызывают функцию.
- Ниже функции есть список функций, которые вызываются этой функцией.

Чтобы получить больше информации вы можете посмотреть в https://sourceware.org/binutils/docs-2.32/gprof/

0
7.12.2019 12:52:42

Обзор методов профилирования C ++

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

Следующая тестовая программа очень проста и выполняет следующие действия:

  • mainзвонки fastи maybe_slow3 раза, один из maybe_slowзвонков медленный

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

  • оба fastи maybe_slowвызов common, который составляет основную часть выполнения программы

  • Интерфейс программы:

    ./main.out [n [seed]]

    и программа делает O(n^2)петли в общей сложности. seedпросто получить другой вывод, не влияя на время выполнения.

main.c

#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>

uint64_t __attribute__ ((noinline)) common(uint64_t n, uint64_t seed) {
    for (uint64_t i = 0; i < n; ++i) {
        seed = (seed * seed) - (3 * seed) + 1;
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) fast(uint64_t n, uint64_t seed) {
    uint64_t max = (n / 10) + 1;
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

uint64_t __attribute__ ((noinline)) maybe_slow(uint64_t n, uint64_t seed, int is_slow) {
    uint64_t max = n;
    if (is_slow) {
        max *= 10;
    }
    for (uint64_t i = 0; i < max; ++i) {
        seed = common(n, (seed * seed) - (3 * seed) + 1);
    }
    return seed;
}

int main(int argc, char **argv) {
    uint64_t n, seed;
    if (argc > 1) {
        n = strtoll(argv[1], NULL, 0);
    } else {
        n = 1;
    }
    if (argc > 2) {
        seed = strtoll(argv[2], NULL, 0);
    } else {
        seed = 0;
    }
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 1);
    seed += fast(n, seed);
    seed += maybe_slow(n, seed, 0);
    seed += fast(n, seed);
    printf("%" PRIX64 "\n", seed);
    return EXIT_SUCCESS;
}

дргоЕ

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

gprof встроен в GCC / binutils, поэтому все, что нам нужно сделать, это скомпилировать с -pgопцией включения gprof. Затем мы обычно запускаем программу с параметром CLI размера, который производит разумную продолжительность в несколько секунд ( 10000):

gcc -pg -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time ./main.out 10000

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

gcc -pg -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
./main.out 10000

Во-первых, timeговорит нам, что время выполнения с и без -pgбыло одинаковым, и это здорово: никакого замедления! Однако я видел сообщения о 2x - 3x замедлениях на сложном программном обеспечении, например, как показано в этом билете .

Поскольку мы скомпилировали, при -pgзапуске программы создается gmon.outфайл с данными профилирования.

Мы можем наблюдать этот файл графически с помощью gprof2dotвопроса: можно ли получить графическое представление результатов gprof?

sudo apt install graphviz
python3 -m pip install --user gprof2dot
gprof main.out > main.gprof
gprof2dot < main.gprof | dot -Tsvg -o output.svg

Здесь gprofинструмент считывает gmon.outинформацию о трассировке и создает отчет, читаемый человеком main.gprof, который gprof2dotзатем читает, чтобы сгенерировать график.

Источник для gprof2dot находится по адресу: https://github.com/jrfonseca/gprof2dot

Мы наблюдаем следующее для -O0бега:

введите описание изображения здесь

и для -O3бега:

введите описание изображения здесь

-O0Выход в значительной степени сам за себя. Например, это показывает, что 3 maybe_slowвызова и их дочерние вызовы занимают 97,56% общего времени выполнения, хотя выполнение самого maybe_slowсебя без дочерних элементов составляет 0,00% общего времени выполнения, то есть почти все время, потраченное на эту функцию, было потрачено на ребенок звонит.

TODO: почему mainотсутствует в -O3выводе, хотя я могу видеть его btв GDB? Пропущенная функция из вывода GProf Я думаю, это потому, что gprof также выполняет выборку в дополнение к скомпилированному инструментарию, и -O3 mainона слишком быстра и не имеет выборок.

Я выбираю вывод SVG вместо PNG, потому что SVG доступен для поиска с помощью Ctrl + F, а размер файла может быть примерно в 10 раз меньше. Кроме того, ширина и высота сгенерированного изображения могут быть огромными с десятками тысяч пикселей для сложного программного обеспечения, и GNOME eog3.28.1 в этом случае работает с ошибками для PNG, тогда как SVG автоматически открываются моим браузером. GIMP 2.8 работал хорошо, см. также:

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

введите описание изображения здесь

Можете ли вы легко найти наиболее критичный стек вызовов со всеми этими крошечными несортированными линиями спагетти, идущими друг на друга? dotЯ уверен, что могут быть лучшие варианты, но я не хочу идти туда сейчас. Что нам действительно нужно, так это соответствующий специальный просмотрщик, но я еще не нашел его:

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

Кроме того, мы также можем наблюдать вывод текста gprofвстроенного инструмента binutils, который мы ранее сохранили в:

cat main.gprof

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

Как только вы поняли формат вывода данных, вы можете уменьшить детализацию, чтобы показывать только данные без учебника, с -bопцией:

gprof -b main.out

В нашем примере выходные данные были для -O0:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls   s/call   s/call  name    
100.35      3.67     3.67   123003     0.00     0.00  common
  0.00      3.67     0.00        3     0.00     0.03  fast
  0.00      3.67     0.00        3     0.00     1.19  maybe_slow

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.27% of 3.67 seconds

index % time    self  children    called     name
                0.09    0.00    3003/123003      fast [4]
                3.58    0.00  120000/123003      maybe_slow [3]
[1]    100.0    3.67    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]    100.0    0.00    3.67                 main [2]
                0.00    3.58       3/3           maybe_slow [3]
                0.00    0.09       3/3           fast [4]
-----------------------------------------------
                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]
-----------------------------------------------
                0.00    0.09       3/3           main [2]
[4]      2.4    0.00    0.09       3         fast [4]
                0.09    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common                  [4] fast                    [3] maybe_slow

и для -O3:

Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total           
 time   seconds   seconds    calls  us/call  us/call  name    
100.52      1.84     1.84   123003    14.96    14.96  common

            Call graph


granularity: each sample hit covers 2 byte(s) for 0.54% of 1.84 seconds

index % time    self  children    called     name
                0.04    0.00    3003/123003      fast [3]
                1.79    0.00  120000/123003      maybe_slow [2]
[1]    100.0    1.84    0.00  123003         common [1]
-----------------------------------------------
                                                 <spontaneous>
[2]     97.6    0.00    1.79                 maybe_slow [2]
                1.79    0.00  120000/123003      common [1]
-----------------------------------------------
                                                 <spontaneous>
[3]      2.4    0.00    0.04                 fast [3]
                0.04    0.00    3003/123003      common [1]
-----------------------------------------------

Index by function name

   [1] common

Как очень краткое резюме для каждого раздела, например:

                0.00    3.58       3/3           main [2]
[3]     97.6    0.00    3.58       3         maybe_slow [3]
                3.58    0.00  120000/123003      common [1]

центрируется вокруг функции с отступом ( maybe_flow). [3]это идентификатор этой функции. Над функцией находятся ее вызывающие, а ниже - вызываемые.

Для -O3, смотрите здесь, как в графическом выводе, что maybe_slowи fastне имеют известного родителя, что означает, что говорит документация <spontaneous>.

Я не уверен, есть ли хороший способ выполнить построчное профилирование с помощью gprof: `gprof` время, потраченное на определенные строки кода

Valgrind Callgrind

valgrind запускает программу через виртуальную машину valgrind. Это делает профилирование очень точным, но также вызывает очень большое замедление программы. Ранее я также упоминал kcachegrind по адресу: Инструменты для получения графического графического вызова функции.

callgrind - это инструмент valgrind для профилирования кода, а kcachegrind - это программа KDE, которая может визуализировать вывод cachegrind.

Сначала мы должны убрать -pgфлаг, чтобы вернуться к нормальной компиляции, в противном случае запуск фактически завершится неудачно Profiling timer expired, и да, это так часто, что я сделал, и для него возник вопрос переполнения стека.

Итак, мы компилируем и запускаем как:

sudo apt install kcachegrind valgrind
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
time valgrind --tool=callgrind valgrind --dump-instr=yes \
  --collect-jumps=yes ./main.out 10000

Я включаю, --dump-instr=yes --collect-jumps=yesпотому что это также выводит информацию, которая позволяет нам просматривать разбивку производительности по конвейеру при относительно небольших дополнительных накладных расходах.

Необычно, timeговорит нам, что выполнение программы заняло 29,5 секунды, поэтому у нас было замедление примерно в 15 раз в этом примере. Понятно, что это замедление станет серьезным ограничением для больших рабочих нагрузок. На упомянутом здесь «примере программного обеспечения реального мира» я наблюдал замедление в 80 раз.

Прогон генерирует файл данных профиля с именем, callgrind.out.<pid>например, callgrind.out.8554в моем случае. Мы просматриваем этот файл с:

kcachegrind callgrind.out.8554

который показывает графический интерфейс, который содержит данные, аналогичные текстовому выводу gprof:

введите описание изображения здесь

Кроме того, если мы перейдем в правую нижнюю вкладку «График вызовов», мы увидим график вызовов, который можно экспортировать, щелкнув его правой кнопкой мыши, чтобы получить следующее изображение с необоснованным количеством белой границы :-)

введите описание изображения здесь

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

В TODO на сложном программном обеспечении C ++ я вижу некоторые записи типа <cycle N>, например, <cycle 11>где я ожидал бы имена функций, что это значит? Я заметил, что есть кнопка «Обнаружение цикла» для включения и выключения, но что это значит?

perf от linux-tools

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

sudo apt install linux-tools
time perf record -g ./main.out 10000

Это добавило 0,2 с к исполнению, поэтому у нас все хорошо, но я все еще не вижу особого интереса после расширения commonузла с помощью стрелки вправо на клавиатуре:

Samples: 7K of event 'cycles:uppp', Event count (approx.): 6228527608     
  Children      Self  Command   Shared Object     Symbol                  
-   99.98%    99.88%  main.out  main.out          [.] common              
     common                                                               
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.01%     0.01%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.01%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.01%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.01%     0.00%  main.out  ld-2.27.so        [.] mprotect            
     0.01%     0.00%  main.out  ld-2.27.so        [.] _dl_map_object      
     0.01%     0.00%  main.out  ld-2.27.so        [.] _xstat              
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x2f3d4f4944555453  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007fff3cfc57ac  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

Затем я пытаюсь сравнить -O0программу, чтобы увидеть, показывает ли это что-нибудь, и только теперь, наконец, я вижу график вызовов:

Samples: 15K of event 'cycles:uppp', Event count (approx.): 12438962281   
  Children      Self  Command   Shared Object     Symbol                  
+   99.99%     0.00%  main.out  [unknown]         [.] 0x04be258d4c544155  
+   99.99%     0.00%  main.out  libc-2.27.so      [.] __libc_start_main   
-   99.99%     0.00%  main.out  main.out          [.] main                
   - main                                                                 
      - 97.54% maybe_slow                                                 
           common                                                         
      - 2.45% fast                                                        
           common                                                         
+   99.96%    99.85%  main.out  main.out          [.] common              
+   97.54%     0.03%  main.out  main.out          [.] maybe_slow          
+    2.45%     0.00%  main.out  main.out          [.] fast                
     0.11%     0.11%  main.out  [kernel]          [k] 0xffffffff8a6009e7  
     0.00%     0.00%  main.out  [unknown]         [k] 0x0000000000000040  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_sysdep_start    
     0.00%     0.00%  main.out  ld-2.27.so        [.] dl_main             
     0.00%     0.00%  main.out  ld-2.27.so        [.] _dl_lookup_symbol_x 
     0.00%     0.00%  main.out  [kernel]          [k] 0xffffffff8a600158  
     0.00%     0.00%  main.out  ld-2.27.so        [.] mmap64              
     0.00%     0.00%  main.out  ld-2.27.
  
7
16.03.2020 12:41:55
По умолчанию в perf-записи используется регистр указателя кадра. Современные компиляторы не записывают адрес фрейма и вместо этого используют регистр в качестве общего назначения. Альтернативой является компиляция с -fno-omit-frame-pointerфлагом или использование другой альтернативы: запись с --call-graph "dwarf"или в --call-graph "lbr"зависимости от вашего сценария.
Jorge Bellon 30.03.2020 15:27:05

использовать программное обеспечение для отладки, как определить, где код работает медленно?

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

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

g++ your_prg.cpp -pgили cc my_program.cpp -g -pgсогласно вашему компилятору

еще не пробовал, но я слышал хорошие вещи о google-perftools. Это определенно стоит попробовать.

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

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

я думаю так

0
18.04.2020 19:37:19