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

1816

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

Габриэль Изенберг
источник
27
Если вы предоставите больше данных о вашем стеке разработки, вы можете получить лучшие ответы. Есть профилировщики от Intel и Sun, но вы должны использовать их компиляторы. Это вариант?
Назгоб
2
На него уже ответили по следующей ссылке: stackoverflow.com/questions/2497211/…
Капил Гупта
4
Большинство ответов - codeпрофилировщики. Однако инверсия приоритетов, наложение псевдонимов в кэше, конфликт ресурсов и т. Д. Могут быть факторами оптимизации и производительности. Я думаю, что люди читают информацию в мой медленный код . Часто задаваемые вопросы ссылаются на эту тему.
бесхитростный шум
3
Раньше я использовал pstack случайным образом, большую часть времени распечатывает наиболее типичный стек, где программа находится большую часть времени, следовательно, указывая на узкое место.
Хосе Мануэль Гомес Альварес

Ответы:

1407

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

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

Просто остановите его несколько раз, и каждый раз смотрите на стек вызовов. Если есть какой-то код, который тратит впустую некоторый процент времени, 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,999 (почти наверняка), а все остальные возможности практически невозможны (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 раза, и она выпадает в голову оба раза, что это говорит вам о вероятном весе монеты? Уважаемый способ ответить на этот вопрос - сказать, что это бета-версия со средним значением (number of hits + 1) / (number of tries + 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%.

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

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

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

Майк Данлавей
источник
292
Это в основном профилировщик выборки для бедного человека, и это здорово, но вы рискуете получить слишком маленький размер выборки, который, возможно, даст вам совершенно ложные результаты.
Crashworks
100
@Crash: Я не буду обсуждать часть «бедняк» :-) Это правда, что точность статистического измерения требует много выборок, но есть две противоречивые цели - измерение и проблемное местоположение. Я сосредотачиваюсь на последнем, для которого вам нужна точность определения местоположения, а не точность измерения. Так, например, в середине стека может быть один вызов функции A (); это составляет 50% времени, но это может быть в другой большой функции B, наряду со многими другими вызовами A (), которые не являются дорогостоящими. Точное суммирование времени функционирования может быть подсказкой, но любой другой пример стека будет точно определять проблему.
Майк Данлавей
41
... мир, кажется, считает, что граф вызовов, аннотированный счетчиком вызовов и / или средним временем, достаточно хорош. Не то. И грустная часть в том, что для тех, кто выбирает стек вызовов, наиболее полезная информация находится прямо перед ними, но они отбрасывают ее в интересах «статистики».
Майк Данлавей
30
Я не хочу не соглашаться с вашей техникой. Понятно, что я очень сильно полагаюсь на профилировщики сэмплирования. Я просто отмечаю, что есть некоторые инструменты, которые делают это в автоматическом режиме, что важно, когда вы достигли точки получения функции с 25% до 15% и вам нужно снизить ее с 1,2% до 0,6%.
Crashworks
13
-1: Идеальная идея, но если вам платят за работу даже в среде с умеренной производительностью, это пустая трата времени каждого. Используйте настоящий профилировщик, чтобы нам не приходилось идти за вами и исправлять проблемы.
Сэм Харвелл
583

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

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

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

Аджай
источник
51
valgrind великолепен, но
имейте в виду,
30
Проверьте также Gprof2Dot для удивительного альтернативного способа визуализации вывода. ./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
Себастьян
2
@neves Да Valgrind просто не очень помогает с точки зрения скорости для профилирования приложений "gstreamer" и "opencv" в реальном времени.
энтузиастик
1
stackoverflow.com/questions/375913/… является частичным решением проблемы скорости.
Тыну Самуэль
3
@Sebastian: gprof2dotсейчас здесь: github.com/jrfonseca/gprof2dot
Джон Zwinck
348

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

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

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

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

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

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

Nazgob
источник
3
Я согласен, что gprof является текущим стандартом. Просто отметим, что Valgrind используется для профилирования утечек памяти и других связанных с памятью аспектов ваших программ, а не для оптимизации скорости.
Билл Ящерица
68
Билл, в наборе vaglrind вы можете найти callgrind и массив. Оба очень полезны для профилирования приложений
dario minonne
7
@ Bill-the-Lizard: некоторые комментарии к gprof : stackoverflow.com/questions/1777556/alternatives-to-gprof/…
Майк Данлавей
6
gprof -pg - это только приблизительное значение профилирования стека вызовов. Он вставляет вызовы mcount, чтобы отслеживать, какие функции вызывают какие другие функции. Он использует стандартную временную выборку для времени. Затем он распределяет время выборки в функции foo () обратно вызывающим функциям foo (), пропорционально количеству вызовов. Так что он не различает звонки с разными затратами.
Крейзи Глеу
1
С помощью clang / clang ++ можно рассмотреть возможность использования профилировщика ЦП gperftools . Предостережение: не сделал этого сам.
einpoklum
257

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

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

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

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

Будет
источник
12
Отличный инструмент! Можно ли как-нибудь получить типичный вид "бабочки", который начинается со стиля "main-> func1-> fun2"? Я не могу понять это ... perf reportкажется, дает мне имена функций с родителями вызова ... (так что это вид перевернутой бабочки)
kizzx2
Уилл, может перфект показать график активности потока; с добавленной информацией о количестве процессоров? Я хочу видеть, когда и какой поток работал на каждом процессоре.
osgx
2
@ kizzx2 - вы можете использовать gprof2dotи perf script. Очень хороший инструмент!
дашесы
2
Даже более новые ядра, такие как 4.13, имеют eBPF для профилирования. См. Brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html и brendangregg.com/ebpf.html
Эндрю Стерн,
Еще одно приятное введение perfв архиве :
li.9/9r927#selection-767.126-767.271
75

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

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

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

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


источник
70

Ответ на запуск 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» показывает, сколько каждой функции потребовалось время, а не вместе с зависимыми.

Тыну Самуэль
источник
9
Теперь по какой-то причине файлы callgrind.out. * Всегда были пустыми. Выполнение callgrind_control -d было полезно для принудительного сброса данных на диск.
Тыну Самуэль
3
Не могу. Мои обычные контексты - это что-то вроде целого MySQL или PHP или чего-то подобного. Часто даже не знаю, что я хочу отделить сначала.
Тыну Самуэль
2
Или в моем случае моя программа фактически загружает кучу данных в кэш LRU, и я не хочу это профилировать. Поэтому при запуске я принудительно загружаю подмножество кеша и профилирую код, используя только эти данные (что позволяет процессору OS + управлять использованием памяти в моем кеше). Это работает, но загрузка этого кеша медленная и интенсивная загрузка ЦП в коде, который я пытаюсь профилировать в другом контексте, поэтому callgrind дает сильно загрязненные результаты.
Код Абоминатор
2
также есть CALLGRIND_TOGGLE_COLLECTвозможность включать / отключать сбор программно; см. stackoverflow.com/a/13700817/288875
Андре Хольцнер,
1
Ничего себе, я не знал, что это существовало, спасибо!
Винсент Фурмонд
59

Это ответ на ответ Назгоба от 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, если это кому-нибудь поможет.

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

Используйте 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)

Ehsan
источник
11

Обзор методов профилирования 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

который показывает GUI, который содержит данные, аналогичные текстовому выводу 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.so        [.] _dl_map_object      
     0.00%     0.00%  main.out  ld-2.27.so        [.] __GI___tunables_init
     0.00%     0.00%  main.out  [unknown]         [.] 0x552e53555f6e653d  
     0.00%     0.00%  main.out  [unknown]         [.] 0x00007ffe1cf20fdb  
     0.00%     0.00%  main.out  ld-2.27.so        [.] _start              

ТОДО: что случилось на -O3казни? Это просто так, maybe_slowи они fastбыли слишком быстры и не получили никаких образцов? Хорошо ли работает с -O3большими программами, выполнение которых занимает больше времени? Я пропустил какую-то опцию CLI? Я узнал, -Fкак управлять частотой дискретизации в герцах, но я увеличил ее до максимально допустимого значения -F 39500(может быть увеличено с помощью sudo), и я до сих пор не вижу четких вызовов.

Еще одна интересная вещь perf- это инструмент FlameGraph от Brendan Gregg, который отображает время стека вызовов очень аккуратно, что позволяет быстро видеть большие вызовы. Инструмент доступен по адресу: https://github.com/brendangregg/FlameGraph. и также упоминается в его перфорации учебника по адресу: http://www.brendangregg.com/perf.html#FlameGraphs Когда я бежал , perfне sudoя ERROR: No stack counts foundтак для сейчас я буду делать это с sudo:

git clone https://github.com/brendangregg/FlameGraph
sudo perf record -F 99 -g -o perf_with_stack.data ./main.out 10000
sudo perf script -i perf_with_stack.data | FlameGraph/stackcollapse-perf.pl | FlameGraph/flamegraph.pl > flamegraph.svg

но в такой простой программы на выходе не очень легко понять, так как мы не можем легко увидеть ни maybe_slowни fastна этом графике:

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

На более сложном примере становится ясно, что означает график:

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

TODO есть журнал [unknown]функций в этом примере, почему это?

Другие интерфейсы perf GUI, которые могут стоить того, включают:

  • Плагин Eclipse Trace Compass: https://www.eclipse.org/tracecompass/

    Но у этого есть недостаток, что вы должны сначала преобразовать данные в общий формат трассировки, что можно сделать с помощью perf data --to-ctf , но его нужно включить во время сборки / иметь perfдостаточно новый, что не подходит для перфорирования. Ubuntu 18.04

  • https://github.com/KDAB/hotspot

    Недостатком этого является то, что, похоже, нет пакета Ubuntu, и для его сборки требуется Qt 5.10, тогда как Ubuntu 18.04 находится на Qt 5.9.

gperftools

Ранее назывался "Инструменты Google Performance", источник: https://github.com/gperftools/gperftools Пример на основе.

Сначала установите gperftools с помощью:

sudo apt install google-perftools

Затем мы можем включить профилировщик ЦП gperftools двумя способами: во время выполнения или во время сборки.

Во время выполнения мы должны передать set LD_PRELOADto point libprofiler.so, который вы можете найти locate libprofiler.so, например, в моей системе:

gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
LD_PRELOAD=/usr/lib/x86_64-linux-gnu/libprofiler.so \
  CPUPROFILE=prof.out ./main.out 10000

В качестве альтернативы, мы можем встроить библиотеку во время соединения, распределяя передачу LD_PRELOADво время выполнения:

gcc -Wl,--no-as-needed,-lprofiler,--as-needed -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
CPUPROFILE=prof.out ./main.out 10000

Смотрите также: gperftools - файл профиля не выгружен

Самый хороший способ просмотреть эти данные, которые я нашел до сих пор, это сделать вывод pprof таким же форматом, который принимает kcachegrind в качестве ввода (да, Valgrind-project-viewer-tool) и использовать kcachegrind для просмотра этого:

google-pprof --callgrind main.out prof.out  > callgrind.out
kcachegrind callgrind.out

После запуска любого из этих методов мы получаем prof.outфайл данных профиля в качестве вывода. Мы можем просмотреть этот файл графически как SVG с:

google-pprof --web main.out prof.out

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

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

Кроме того, мы также можем получить некоторые текстовые данные с:

google-pprof --text main.out prof.out

который дает:

Using local file main.out.
Using local file prof.out.
Total: 187 samples
     187 100.0% 100.0%      187 100.0% common
       0   0.0% 100.0%      187 100.0% __libc_start_main
       0   0.0% 100.0%      187 100.0% _start
       0   0.0% 100.0%        4   2.1% fast
       0   0.0% 100.0%      187 100.0% main
       0   0.0% 100.0%      183  97.9% maybe_slow

Смотрите также: Как использовать Google Perf Tools

Протестировано в Ubuntu 18.04, gprof2dot 2019.11.30, valgrind 3.13.0, perf 4.15.18, ядре Linux 4.15.0, FLameGraph 1a0dc6985aad06e76857cf2a354bd5ba0c9ce96b, gperftools 2.5-2.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
источник
2
По умолчанию в perf-записи используется регистр указателя кадра. Современные компиляторы не записывают адрес фрейма и вместо этого используют регистр в качестве общего назначения. Альтернативой является компиляция с -fno-omit-frame-pointerфлагом или использование другой альтернативы: запись с --call-graph "dwarf"или в --call-graph "lbr"зависимости от вашего сценария.
Хорхе Беллон
5

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

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

fwyzard
источник
Это выглядит интересно, но не компилируется с GCC 9.2. (Debian / Sid) Я сделал проблему на github.
Василий Старынкевич
5

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

  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 type build в CMake, чтобы получить значимый вывод) , Я слышал, что TAU схожи по возможностям.

raovgarimella
источник
4

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

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

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

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

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

NB

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

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

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

поисковая оптимизация
источник
2
+1 Метод бедняка работает так же хорошо для привязки ввода / вывода, как и для привязки к процессору, и я рекомендую выполнять всю настройку производительности в режиме отладки. Когда вы закончите настройку, включите RELEASE. Это улучшит ситуацию, если программа в вашем коде связана с процессором. Вот грубое, но короткое видео процесса.
Майк Данлавей,
3
Я бы не использовал сборки DEBUG для профилирования производительности. Часто я видел, что критически важные для производительности части в режиме DEBUG полностью оптимизируются в режиме выпуска. Другая проблема - использование утверждений в отладочном коде, которые добавляют шум производительности.
gast128
3
Ты вообще прочитал мой пост? «Если вам нужна производительность (например, чувствительная ко времени) в режиме RELEASE, отключите функции отладчика, чтобы сохранить работоспособность», «Затем переключитесь в режим RELEASE и комментируйте сомнительные разделы вашего кода (заглушайте его ничем), пока не увидите изменения в производительности. " Я сказал проверить возможные проблемные области в режиме отладки и проверить эти проблемы в режиме выпуска, чтобы избежать ошибки, которую вы упомянули.
SEO
2

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

https://gitlab.com/Neurochrom/iprof

https://github.com/Neurochrom/iprof

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

N3UR0CHR0M
источник
2

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

BullyWiiPlaza
источник
1

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

Он находится на 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 , а затем устанавливает запись в буфер. У нас также есть атомарный инкремент для безопасного поиска индекса, в котором будет храниться событие журнала. Конечно буфер является круглым.

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

Soks
источник
1

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

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

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

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

u__
источник
1

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

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

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

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

Mehdi_Pejvak
источник
0

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

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

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

Вэй
источник
0

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

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

подобно тому, как циклы нежелательного перераспределения, переполнения буфера, поиск, утечки памяти и т. д. операции потребляют большую мощность выполнения, что отрицательно скажется на производительности кода, обязательно добавьте -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 или инструмент отладчика, чтобы прочитать этот файл. Это даст вам графический анализ вещей с результатами, например, какие строки стоят сколько.

я думаю так


источник