Если вы предоставите больше данных о вашем стеке разработки, вы можете получить лучшие ответы. Есть профилировщики от Intel и Sun, но вы должны использовать их компиляторы. Это вариант?
Большинство ответов - codeпрофилировщики. Однако инверсия приоритетов, наложение псевдонимов в кэше, конфликт ресурсов и т. Д. Могут быть факторами оптимизации и производительности. Я думаю, что люди читают информацию в мой медленный код . Часто задаваемые вопросы ссылаются на эту тему.
Раньше я использовал pstack случайным образом, большую часть времени распечатывает наиболее типичный стек, где программа находится большую часть времени, следовательно, указывая на узкое место.
Хосе Мануэль Гомес Альварес
Ответы:
1407
Если ваша цель - использовать профилировщик, воспользуйтесь одним из предложенных.
Однако, если вы спешите и можете вручную прервать программу под отладчиком, пока она субъективно медленная, существует простой способ найти проблемы с производительностью.
Просто остановите его несколько раз, и каждый раз смотрите на стек вызовов. Если есть какой-то код, который тратит впустую некоторый процент времени, 20% или 50% или что-то еще, то есть вероятность, что вы поймаете его в действии на каждом образце. Таким образом, это примерно процент образцов, на которых вы его увидите. Там не требуется образованное предположение. Если у вас есть предположение относительно проблемы, это докажет или опровергнет ее.
У вас может быть несколько проблем с производительностью разных размеров. Если вы уберете какой-либо из них, остальные будут занимать больший процент, и их будет легче обнаружить при последующих проходах. Этот эффект увеличения в сочетании с несколькими проблемами может привести к действительно огромным факторам ускорения.
Предостережение : программисты склонны скептически относиться к этой технике, если они сами ее не использовали. Они скажут, что профилировщики предоставляют вам эту информацию, но это верно только в том случае, если они производят выборку всего стека вызовов, а затем позволяют исследовать случайный набор выборок. (Сводные данные - то, где понимание потеряно.) Графики вызовов не дают вам ту же информацию, потому что
Они не суммируют на уровне инструкции, и
Они дают запутанные резюме при наличии рекурсии.
Они также скажут, что это работает только на игрушечных программах, когда на самом деле это работает на любой программе, и, кажется, лучше работает на больших программах, потому что у них, как правило, больше проблем для поиска. Они скажут, что иногда он находит вещи, которые не являются проблемами, но это правда, если вы видите что-то один раз . Если вы видите проблему более чем на одном образце, это реально.
PS Это также может быть сделано в многопоточных программах, если есть способ собрать образцы стека вызовов пула потоков в определенный момент времени, как в Java.
PPS Как грубое обобщение, чем больше уровней абстракции в вашем программном обеспечении, тем больше вероятность того, что вы обнаружите, что это является причиной проблем с производительностью (и возможностью получить ускорение).
Добавлено : Это может быть неочевидно, но техника выборки из стека одинаково хорошо работает при наличии рекурсии. Причина в том, что время, которое будет сэкономлено удалением инструкции, аппроксимируется долей содержащих ее выборок, независимо от того, сколько раз это может произойти в выборке.
Другое возражение, которое я часто слышу, звучит так: « Это остановит что-то случайное и упустит реальную проблему ». Это происходит из-за наличия предварительного представления о том, что является реальной проблемой. Ключевым свойством проблем производительности является то, что они не поддаются ожиданиям. Выборка говорит вам, что что-то является проблемой, и ваша первая реакция - неверие. Это естественно, но вы можете быть уверены, что если проблема найдется, то это реально, и наоборот.
Добавлено : Позвольте мне сделать байесовское объяснение того, как это работает. Предположим, что есть какая-то инструкция I(вызов или иное), которая находится в стеке вызовов некоторую долю fвремени (и, следовательно, стоит так дорого). Для простоты предположим, что мы не знаем, что это fтакое, но предположим, что это либо 0,1, 0,2, 0,3, ... 0,9, 1,0, а предыдущая вероятность каждой из этих возможностей равна 0,1, поэтому все эти затраты одинаково вероятны априори.
Затем предположим, что мы берем только 2 выборки из стека и видим инструкцию Iдля обоих образцов, обозначенную как наблюдение o=2/2. Это дает нам новые оценки частоты fв Iсоответствии с этим:
Prior
P(f=x) x P(o=2/2|f=x) P(o=2/2&&f=x) P(o=2/2&&f >= x) P(f >= x | o=2/2)0.1110.10.10.259740260.10.90.810.0810.1810.470129870.10.80.640.0640.2450.6363636360.10.70.490.0490.2940.7636363640.10.60.360.0360.330.8571428570.10.50.250.0250.3550.9220779220.10.40.160.0160.3710.9636363640.10.30.090.0090.380.9870129870.10.20.040.0040.3840.9974025970.10.10.010.0010.3851
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.001110.0010.0010.0727272730.0010.90.810.000810.001810.1316363640.0010.80.640.000640.002450.1781818180.0010.70.490.000490.002940.2138181820.0010.60.360.000360.00330.240.0010.50.250.000250.003550.2581818180.0010.40.160.000160.003710.2698181820.0010.30.090.000090.00380.2763636360.0010.20.040.000040.003840.2792727270.9910.10.010.009910.013751
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%.
Вот неординарная иллюстрация разницы между проверкой измерений и исследованием образцов стеков. Узким местом может быть один такой большой шарик или множество маленьких, это не имеет значения.
Измерение горизонтальное; он говорит вам, какую часть времени занимают определенные подпрограммы. Выборка вертикальная. Если есть какой-либо способ избежать того, что в данный момент делает вся программа, и если вы видите это во втором примере , вы нашли узкое место. Вот в чем разница - видя всю причину затраченного времени, а не только сколько.
Это в основном профилировщик выборки для бедного человека, и это здорово, но вы рискуете получить слишком маленький размер выборки, который, возможно, даст вам совершенно ложные результаты.
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инструмент для чтения этого файла. Это даст вам графический анализ вещей с результатами, например, какие строки стоят сколько.
Я согласен, что gprof является текущим стандартом. Просто отметим, что Valgrind используется для профилирования утечек памяти и других связанных с памятью аспектов ваших программ, а не для оптимизации скорости.
Билл Ящерица
68
Билл, в наборе vaglrind вы можете найти callgrind и массив. Оба очень полезны для профилирования приложений
gprof -pg - это только приблизительное значение профилирования стека вызовов. Он вставляет вызовы mcount, чтобы отслеживать, какие функции вызывают какие другие функции. Он использует стандартную временную выборку для времени. Затем он распределяет время выборки в функции foo () обратно вызывающим функциям foo (), пропорционально количеству вызовов. Так что он не различает звонки с разными затратами.
Крейзи Глеу
1
С помощью clang / clang ++ можно рассмотреть возможность использования профилировщика ЦП gperftools . Предостережение: не сделал этого сам.
einpoklum
257
Более новые ядра (например, новейшие ядра Ubuntu) поставляются с новыми инструментами 'perf' ( apt-get install linux-tools) AKA perf_events .
Важно то, что эти инструменты могут быть профилированием системы, а не просто профилированием процесса - они могут показывать взаимодействие между потоками, процессами и ядром и позволяют понять зависимости планирования и ввода-вывода между процессами.
Отличный инструмент! Можно ли как-нибудь получить типичный вид "бабочки", который начинается со стиля "main-> func1-> fun2"? Я не могу понять это ... perf reportкажется, дает мне имена функций с родителями вызова ... (так что это вид перевернутой бабочки)
kizzx2
Уилл, может перфект показать график активности потока; с добавленной информацией о количестве процессоров? Я хочу видеть, когда и какой поток работал на каждом процессоре.
osgx
2
@ kizzx2 - вы можете использовать gprof2dotи perf script. Очень хороший инструмент!
Я бы использовал Valgrind и Callgrind в качестве основы для своего набора инструментов профилирования. Важно знать, что Valgrind - это виртуальная машина:
(Википедия) Valgrind по сути является виртуальной машиной, использующей технологии JIT-компиляции, в том числе динамическую перекомпиляцию. Ничто из оригинальной программы никогда не запускается непосредственно на главном процессоре. Вместо этого Valgrind сначала переводит программу во временную, более простую форму, называемую промежуточным представлением (IR), которая является независимой от процессора формой на основе SSA. После преобразования инструмент (см. Ниже) может свободно выполнять любые преобразования, которые он желает, на IR, прежде чем Valgrind переведет IR обратно в машинный код и позволит хост-процессору запустить его.
Callgrind - это профилировщик, основанный на этом. Основным преимуществом является то, что вам не нужно запускать приложение в течение нескольких часов, чтобы получить надежный результат. Даже одной секунды достаточно для получения надежных и надежных результатов, потому что Callgrind - не зондирующий профилировщик.
Другой инструмент, основанный на Вальгринде, - Массив. Я использую его для профилирования использования памяти кучи. Это прекрасно работает. Что он делает, так это то, что он дает вам снимки использования памяти - детальная информация, ЧТО содержит какой процент памяти, и ВОЗ поместила его туда. Такая информация доступна в разные моменты времени запуска приложения.
Ответ на запуск valgrind --tool=callgrindне совсем полный без некоторых вариантов. Обычно мы не хотим профилировать 10 минут медленного запуска в Valgrind и хотим профилировать нашу программу, когда она выполняет какую-то задачу.
Так что это то, что я рекомендую. Сначала запустите программу:
Теперь, когда это работает, и мы хотим начать профилирование, мы должны запустить в другом окне:
callgrind_control -i on
Это включает профилирование. Чтобы выключить и остановить всю задачу, мы можем использовать:
callgrind_control -k
Теперь у нас есть несколько файлов с именем callgrind.out. * В текущем каталоге. Чтобы увидеть результаты профилирования, используйте:
kcachegrind callgrind.out.*
Я рекомендую в следующем окне нажать на заголовок столбца «Self», иначе он показывает, что «main ()» является наиболее трудоемкой задачей. «Self» показывает, сколько каждой функции потребовалось время, а не вместе с зависимыми.
Теперь по какой-то причине файлы callgrind.out. * Всегда были пустыми. Выполнение callgrind_control -d было полезно для принудительного сброса данных на диск.
Тыну Самуэль
3
Не могу. Мои обычные контексты - это что-то вроде целого MySQL или PHP или чего-то подобного. Часто даже не знаю, что я хочу отделить сначала.
Тыну Самуэль
2
Или в моем случае моя программа фактически загружает кучу данных в кэш LRU, и я не хочу это профилировать. Поэтому при запуске я принудительно загружаю подмножество кеша и профилирую код, используя только эти данные (что позволяет процессору OS + управлять использованием памяти в моем кеше). Это работает, но загрузка этого кеша медленная и интенсивная загрузка ЦП в коде, который я пытаюсь профилировать в другом контексте, поэтому callgrind дает сильно загрязненные результаты.
Я использовал Gprof последние пару дней и уже нашел три существенных ограничения, одно из которых я не видел нигде (пока), документированных:
Он не работает должным образом для многопоточного кода, если вы не используете обходной путь
Граф вызовов запутывается указателями на функции. Пример: у меня есть вызываемая функция, multithread()которая позволяет мне выполнять многопоточность указанной функции по указанному массиву (оба передаются в качестве аргументов). Однако Gprof рассматривает все вызовы multithread()как эквивалентные в целях вычисления времени, проведенного у детей. Поскольку некоторые функции, которые я передаю, multithread()занимают намного больше времени, чем другие, мои графы вызовов в основном бесполезны. (Для тех, кто интересуется, является ли здесь проблема с многопоточностью: нет, multithread()может, необязательно, и в этом случае выполнил все последовательно только в вызывающем потоке).
Он говорит здесь , что «... цифры ЧИСЛО-вызовов получены путем подсчета, не пробуя. Они абсолютно точны ...». Тем не менее, я нахожу свой график вызовов, показывающий мне 5345859132 + 784984078 как статистику вызовов для моей наиболее вызываемой функции, где первый номер должен быть прямым вызовом, а второй рекурсивный вызов (который все из себя). Поскольку это означало, что у меня была ошибка, я вставил в код длинные (64-битные) счетчики и повторил тот же прогон. Мои счета: 5345859132 прямых и 78094395406 саморекурсивных вызовов. Там много цифр, поэтому я укажу, что измеряемые мной рекурсивные вызовы составляют 78 млрд. Против 784 млн. Из Gprof: коэффициент в 100 разнится. Оба прогона были однопотоковыми и неоптимизированными, один компилировался, -gа другой -pg.
Это был GNU Gprof (GNU Binutils для Debian) 2.18.0.20080103, работающий под 64-битным Debian Lenny, если это кому-нибудь поможет.
Да, это делает выборку, но не для числа звонков. Интересно, что следование по вашей ссылке в конечном итоге привело меня к обновленной версии страницы руководства, на которую я ссылался в своем посте, по новому 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)
В этом ответе я буду использовать несколько различных инструментов для анализа нескольких очень простых тестовых программ, чтобы конкретно сравнить, как эти инструменты работают.
Следующая тестовая программа очень проста и выполняет следующие действия:
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):
По образовательным причинам мы также проведем прогон без включенной оптимизации. Обратите внимание, что на практике это бесполезно, так как вы обычно заботитесь только об оптимизации производительности оптимизированной программы:
Во-первых, timeговорит нам, что время выполнения с и без -pgбыло одинаковым, и это здорово: никакого замедления! Однако я видел сообщения о 2x - 3x замедлениях на сложном программном обеспечении, например, как показано в этом билете .
Поскольку мы скомпилировали, при -pgзапуске программы создается gmon.outфайл с данными профилирования.
Здесь gprofинструмент считывает gmon.outинформацию о трассировке и создает отчет, читаемый человеком main.gprof, который gprof2dotзатем считывает для создания графика.
-O0Выход в значительной степени сам за себя. Например, это показывает, что 3 maybe_slowвызова и их дочерние вызовы занимают 97,56% общего времени выполнения, хотя выполнение самого maybe_slowсебя без дочерних элементов составляет 0,00% общего времени выполнения, то есть почти все время, потраченное на эту функцию, было потрачено на ребенок звонит.
TODO: почему mainотсутствует в -O3выводе, хотя я могу видеть его btв GDB? Пропущенная функция из вывода GProf Я думаю, это потому, что gprof также выполняет выборку в дополнение к скомпилированному инструментарию, и -O3mainона слишком быстра и не имеет выборок.
Я выбираю вывод 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.353.673.671230030.000.00 common
0.003.670.0030.000.03 fast
0.003.670.0030.001.19 maybe_slow
Call graph
granularity: each sample hit covers 2 byte(s)for0.27% of 3.67 seconds
index % time self children called name
0.090.003003/123003 fast [4]3.580.00120000/123003 maybe_slow [3][1]100.03.670.00123003 common [1]-----------------------------------------------<spontaneous>[2]100.00.003.67 main [2]0.003.583/3 maybe_slow [3]0.000.093/3 fast [4]-----------------------------------------------0.003.583/3 main [2][3]97.60.003.583 maybe_slow [3]3.580.00120000/123003 common [1]-----------------------------------------------0.000.093/3 main [2][4]2.40.000.093 fast [4]0.090.003003/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.521.841.8412300314.9614.96 common
Call graph
granularity: each sample hit covers 2 byte(s)for0.54% of 1.84 seconds
index % time self children called name
0.040.003003/123003 fast [3]1.790.00120000/123003 maybe_slow [2][1]100.01.840.00123003 common [1]-----------------------------------------------<spontaneous>[2]97.60.001.79 maybe_slow [2]1.790.00120000/123003 common [1]-----------------------------------------------<spontaneous>[3]2.40.000.04 fast [3]0.040.003003/123003 common [1]-----------------------------------------------Index by function name
[1] common
Как очень краткое резюме для каждого раздела, например:
0.003.583/3 main [2][3]97.60.003.583 maybe_slow [3]3.580.00120000/123003 common [1]
центрируется вокруг функции с отступом ( maybe_flow). [3]это идентификатор этой функции. Над функцией находятся ее вызывающие, а ниже - вызываемые.
Для -O3, смотрите здесь, как в графическом выводе, что maybe_slowи fastне имеют известного родителя, что означает то, что говорит документация <spontaneous>.
valgrind запускает программу через виртуальную машину valgrind. Это делает профилирование очень точным, но также вызывает очень большое замедление программы. Ранее я также упоминал kcachegrind по адресу: Инструменты для получения графического графического вызова функции.
callgrind - это инструмент valgrind для профилирования кода, а kcachegrind - это программа KDE, которая может визуализировать вывод cachegrind.
Сначала мы должны убрать -pgфлаг, чтобы вернуться к нормальной компиляции, в противном случае запуск фактически завершится неудачно Profiling timer expired, и да, это так часто, что я сделал, и для него возник вопрос переполнения стека.
Я включаю, --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узла с помощью стрелки вправо на клавиатуре:
ТОДО: что случилось на -O3казни? Это просто так, maybe_slowи они fastбыли слишком быстры и не получили никаких образцов? Хорошо ли работает с -O3большими программами, выполнение которых занимает больше времени? Я пропустил какую-то опцию CLI? Я узнал, -Fкак управлять частотой дискретизации в герцах, но я увеличил ее до максимально допустимого значения -F 39500(может быть увеличено с помощью sudo), и я до сих пор не вижу четких вызовов.
Но у этого есть недостаток, что вы должны сначала преобразовать данные в общий формат трассировки, что можно сделать с помощью perf data --to-ctf , но его нужно включить во время сборки / иметь perfдостаточно новый, что не подходит для перфорирования. Ubuntu 18.04
Самый хороший способ просмотреть эти данные, которые я нашел до сих пор, это сделать вывод pprof таким же форматом, который принимает kcachegrind в качестве ввода (да, Valgrind-project-viewer-tool) и использовать kcachegrind для просмотра этого:
После запуска любого из этих методов мы получаем 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
187100.0%100.0%187100.0% common
00.0%100.0%187100.0% __libc_start_main
00.0%100.0%187100.0% _start
00.0%100.0%42.1% fast
00.0%100.0%187100.0% main
00.0%100.0%18397.9% maybe_slow
По умолчанию в perf-записи используется регистр указателя кадра. Современные компиляторы не записывают адрес фрейма и вместо этого используют регистр в качестве общего назначения. Альтернативой является компиляция с -fno-omit-frame-pointerфлагом или использование другой альтернативы: запись с --call-graph "dwarf"или в --call-graph "lbr"зависимости от вашего сценария.
Хорхе Беллон
5
Для однопоточных программ вы можете использовать igprof , Ignominous Profiler: https://igprof.org/ .
Это профилировщик выборки, аналогичный ответу ... long ... Mike Dunlavey, который будет обернуть результаты в дерево стека вызовов с возможностью просмотра, снабженное комментариями с указанием времени или памяти, затраченных на каждую функцию, либо нарастающую, либо за функции.
Это выглядит интересно, но не компилируется с GCC 9.2. (Debian / Sid) Я сделал проблему на github.
Василий Старынкевич
5
Также стоит упомянуть
HPCToolkit ( http://hpctoolkit.org/ ) - с открытым исходным кодом, работает для параллельных программ и имеет графический интерфейс для просмотра результатов несколькими способами
Я использовал HPCToolkit и VTune, и они очень эффективны при поиске длинного полюса в палатке и не нуждаются в перекомпиляции вашего кода (за исключением того, что вы должны использовать -g -O или RelWithDebInfo type build в CMake, чтобы получить значимый вывод) , Я слышал, что TAU схожи по возможностям.
Вот два метода, которые я использую для ускорения моего кода:
Для приложений, связанных с процессором:
Используйте профилировщик в режиме отладки для определения сомнительных частей вашего кода
Затем переключитесь в режим RELEASE и закомментируйте сомнительные разделы вашего кода (заглушите его ничем), пока не увидите изменения в производительности.
Для приложений ввода-вывода:
Используйте профилировщик в режиме RELEASE для определения сомнительных частей вашего кода.
NB
Если у вас нет профилировщика, используйте профилировщик для бедняка. Хит пауза при отладке вашего приложения. Большинство комплектов разработчика будут разбиты на сборки с закомментированными номерами строк. По статистике вы можете приземлиться в регионе, который потребляет большую часть ваших циклов ЦП.
Для CPU причина профилирования в режиме DEBUG заключается в том, что, если вы попробовали профилирование в режиме RELEASE , компилятор собирается уменьшить математические, векторизованные циклы и встроенные функции, которые имеют тенденцию превращать ваш код в не отображаемый беспорядок при его сборке. Непоправимый беспорядок означает, что ваш профилировщик не сможет четко определить, что занимает так много времени, поскольку сборка может не соответствовать оптимизируемому исходному коду . Если вам нужна производительность (например, чувствительная ко времени) в режиме RELEASE , отключите функции отладчика, чтобы сохранить работоспособность.
Для привязки к вводу / выводу профилировщик все еще может идентифицировать операции ввода / вывода в режиме RELEASE, потому что операции ввода / вывода либо внешне связаны с общей библиотекой (большую часть времени), либо в худшем случае приведут к системной вектор прерывания вызова (который также легко определяется профилировщиком).
+1 Метод бедняка работает так же хорошо для привязки ввода / вывода, как и для привязки к процессору, и я рекомендую выполнять всю настройку производительности в режиме отладки. Когда вы закончите настройку, включите RELEASE. Это улучшит ситуацию, если программа в вашем коде связана с процессором. Вот грубое, но короткое видео процесса.
Майк Данлавей,
3
Я бы не использовал сборки DEBUG для профилирования производительности. Часто я видел, что критически важные для производительности части в режиме DEBUG полностью оптимизируются в режиме выпуска. Другая проблема - использование утверждений в отладочном коде, которые добавляют шум производительности.
gast128
3
Ты вообще прочитал мой пост? «Если вам нужна производительность (например, чувствительная ко времени) в режиме RELEASE, отключите функции отладчика, чтобы сохранить работоспособность», «Затем переключитесь в режим RELEASE и комментируйте сомнительные разделы вашего кода (заглушайте его ничем), пока не увидите изменения в производительности. " Я сказал проверить возможные проблемные области в режиме отладки и проверить эти проблемы в режиме выпуска, чтобы избежать ошибки, которую вы упомянули.
Он кроссплатформенный и позволяет не измерять производительность вашего приложения также в режиме реального времени. Вы можете даже соединить это с живым графиком. Полный отказ от ответственности: я автор.
Вы можете использовать каркас журналирования, например, loguruтак как он включает метки времени и общее время безотказной работы, которые можно использовать для профилирования:
На работе у нас есть действительно хороший инструмент, который помогает нам отслеживать то, что мы хотим с точки зрения планирования. Это было полезно много раз.
Он находится на C ++ и должен быть настроен в соответствии с вашими потребностями. К сожалению, я не могу поделиться кодом, только понятиями. Вы используете «большой» volatileбуфер, содержащий метки времени и идентификатор события, которые вы можете выгрузить после вскрытия или после остановки системы ведения журнала (и, например, выгрузить ее в файл).
Вы получаете так называемый большой буфер со всеми данными, а небольшой интерфейс анализирует его и показывает события с именем (вверх / вниз + значение), как осциллограф с цветами (настраивается в .hppфайле).
Вы настраиваете количество генерируемых событий, чтобы сосредоточиться исключительно на том, что вы хотите. Это очень помогло нам в планировании проблем при использовании требуемого количества ЦП в зависимости от количества зарегистрированных событий в секунду.
Вам нужно 3 файла:
toolname.hpp // interface
toolname.cpp // code
tool_events_id.hpp // Events ID
Концепция состоит в том, чтобы определять события tool_events_id.hppследующим образом:
probeФункция использует несколько сборочных линий , чтобы получить тактовую метку времени ASAP , а затем устанавливает запись в буфер. У нас также есть атомарный инкремент для безопасного поиска индекса, в котором будет храниться событие журнала. Конечно буфер является круглым.
Надеюсь, что идея не запутана отсутствием примера кода.
На самом деле немного удивлен, что многие не упоминали о google / benchmark , хотя немного сложно связать определенную область кода, особенно если база кода немного большая, однако я нашел это действительно полезным при использовании в сочетании сcallgrind
ИМХО, идентификация части, которая вызывает узкое место, является ключом здесь. Однако сначала я постараюсь ответить на следующие вопросы и выбрать инструмент, основанный на этом.
мой алгоритм правильный?
есть ли замки, которые оказываются узкими местами?
Есть ли определенный раздел кода, который оказывается виновником?
как насчет ввода-вывода, обрабатываются и оптимизированы?
valgrindс комбинацией callrindи kcachegrindдолжен обеспечить достойную оценку по пунктам выше, и как только будет установлено, что есть проблемы с каким-то разделом кода, я бы посоветовал сделать микропробную оценку google benchmark- это хорошее место для начала.
Используйте -pgфлаг при компиляции и компоновке кода и запустите исполняемый файл. Во время выполнения этой программы данные профилирования собираются в файле a.out.
Существует два разных типа профилирования
1 - Плоское профилирование:
выполнив команду, gprog --flat-profile a.outвы получите следующие данные
- какой процент от общего времени было потрачено на функцию,
- сколько секунд было потрачено на функцию, включая и исключая вызовы подфункций,
- количество звонки,
- среднее время звонка.
2- представьте
нам команду gprof --graph a.outдля получения следующих данных для каждой функции, которая включает:
- В каждом разделе одна функция помечена индексным номером.
- Выше функции есть список функций, которые вызывают функцию.
- Ниже функции есть список функций, которые вызываются этой функцией.
Поскольку никто не упомянул Arm MAP, я бы добавил, что лично я успешно использовал Map для профилирования научной программы на C ++.
Arm MAP - это профилировщик для параллельных, многопоточных или однопоточных кодов C, C ++, Fortran и F90. Он обеспечивает углубленный анализ и точное определение узкого места в исходной строке. В отличие от большинства профилировщиков, он предназначен для профилирования pthreads, OpenMP или MPI для параллельного и многопоточного кода.
использовать программное обеспечение
для отладки, как определить, где код работает медленно?
просто думайте, что у вас есть препятствие, пока вы находитесь в движении, тогда это снизит вашу скорость
подобно тому, как циклы нежелательного перераспределения, переполнения буфера, поиск, утечки памяти и т. д. операции потребляют большую мощность выполнения, что отрицательно скажется на производительности кода, обязательно добавьте -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 или инструмент отладчика, чтобы прочитать этот файл. Это даст вам графический анализ вещей с результатами, например, какие строки стоят сколько.
code
профилировщики. Однако инверсия приоритетов, наложение псевдонимов в кэше, конфликт ресурсов и т. Д. Могут быть факторами оптимизации и производительности. Я думаю, что люди читают информацию в мой медленный код . Часто задаваемые вопросы ссылаются на эту тему.Ответы:
Если ваша цель - использовать профилировщик, воспользуйтесь одним из предложенных.
Однако, если вы спешите и можете вручную прервать программу под отладчиком, пока она субъективно медленная, существует простой способ найти проблемы с производительностью.
Просто остановите его несколько раз, и каждый раз смотрите на стек вызовов. Если есть какой-то код, который тратит впустую некоторый процент времени, 20% или 50% или что-то еще, то есть вероятность, что вы поймаете его в действии на каждом образце. Таким образом, это примерно процент образцов, на которых вы его увидите. Там не требуется образованное предположение. Если у вас есть предположение относительно проблемы, это докажет или опровергнет ее.
У вас может быть несколько проблем с производительностью разных размеров. Если вы уберете какой-либо из них, остальные будут занимать больший процент, и их будет легче обнаружить при последующих проходах. Этот эффект увеличения в сочетании с несколькими проблемами может привести к действительно огромным факторам ускорения.
Предостережение : программисты склонны скептически относиться к этой технике, если они сами ее не использовали. Они скажут, что профилировщики предоставляют вам эту информацию, но это верно только в том случае, если они производят выборку всего стека вызовов, а затем позволяют исследовать случайный набор выборок. (Сводные данные - то, где понимание потеряно.) Графики вызовов не дают вам ту же информацию, потому что
Они также скажут, что это работает только на игрушечных программах, когда на самом деле это работает на любой программе, и, кажется, лучше работает на больших программах, потому что у них, как правило, больше проблем для поиска. Они скажут, что иногда он находит вещи, которые не являются проблемами, но это правда, если вы видите что-то один раз . Если вы видите проблему более чем на одном образце, это реально.
PS Это также может быть сделано в многопоточных программах, если есть способ собрать образцы стека вызовов пула потоков в определенный момент времени, как в Java.
PPS Как грубое обобщение, чем больше уровней абстракции в вашем программном обеспечении, тем больше вероятность того, что вы обнаружите, что это является причиной проблем с производительностью (и возможностью получить ускорение).
Добавлено : Это может быть неочевидно, но техника выборки из стека одинаково хорошо работает при наличии рекурсии. Причина в том, что время, которое будет сэкономлено удалением инструкции, аппроксимируется долей содержащих ее выборок, независимо от того, сколько раз это может произойти в выборке.
Другое возражение, которое я часто слышу, звучит так: « Это остановит что-то случайное и упустит реальную проблему ». Это происходит из-за наличия предварительного представления о том, что является реальной проблемой. Ключевым свойством проблем производительности является то, что они не поддаются ожиданиям. Выборка говорит вам, что что-то является проблемой, и ваша первая реакция - неверие. Это естественно, но вы можете быть уверены, что если проблема найдется, то это реально, и наоборот.
Добавлено : Позвольте мне сделать байесовское объяснение того, как это работает. Предположим, что есть какая-то инструкция
I
(вызов или иное), которая находится в стеке вызовов некоторую долюf
времени (и, следовательно, стоит так дорого). Для простоты предположим, что мы не знаем, что этоf
такое, но предположим, что это либо 0,1, 0,2, 0,3, ... 0,9, 1,0, а предыдущая вероятность каждой из этих возможностей равна 0,1, поэтому все эти затраты одинаково вероятны априори.Затем предположим, что мы берем только 2 выборки из стека и видим инструкцию
I
для обоих образцов, обозначенную как наблюдениеo=2/2
. Это дает нам новые оценки частотыf
вI
соответствии с этим:В последнем столбце говорится, что, например, вероятность того, что
f
> 0,5, составляет 92% по сравнению с предыдущим предположением 60%.Предположим, что предыдущие предположения отличаются. Предположим, мы предполагаем,
P(f=0.1)
что 0,999 (почти наверняка), а все остальные возможности практически невозможны (0,001). Другими словами, наша предварительная уверенность в том, чтоI
это дешево. Тогда мы получим:Теперь это говорит о
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%.
Вот неординарная иллюстрация разницы между проверкой измерений и исследованием образцов стеков. Узким местом может быть один такой большой шарик или множество маленьких, это не имеет значения.
Измерение горизонтальное; он говорит вам, какую часть времени занимают определенные подпрограммы. Выборка вертикальная. Если есть какой-либо способ избежать того, что в данный момент делает вся программа, и если вы видите это во втором примере , вы нашли узкое место. Вот в чем разница - видя всю причину затраченного времени, а не только сколько.
источник
Вы можете использовать Valgrind со следующими опциями
Это сгенерирует файл с именем
callgrind.out.x
. Затем вы можете использоватьkcachegrind
инструмент для чтения этого файла. Это даст вам графический анализ вещей с результатами, например, какие строки стоят сколько.источник
./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
gprof2dot
сейчас здесь: github.com/jrfonseca/gprof2dotЯ полагаю, вы используете GCC. Стандартным решением будет профилирование с помощью gprof .
Не забудьте добавить
-pg
в компиляцию перед профилированием:Я еще не пробовал, но я слышал хорошие вещи о google-perftools . Это определенно стоит попробовать.
Связанный вопрос здесь .
Несколько других модных слов, если вы
gprof
этого не сделаете: Valgrind , Intel VTune , Sun DTrace .источник
Более новые ядра (например, новейшие ядра Ubuntu) поставляются с новыми инструментами 'perf' (
apt-get install linux-tools
) AKA perf_events .Они идут с классическими профилировщиками сэмплирования ( man-page ), а также с отличной временной диаграммой !
Важно то, что эти инструменты могут быть профилированием системы, а не просто профилированием процесса - они могут показывать взаимодействие между потоками, процессами и ядром и позволяют понять зависимости планирования и ввода-вывода между процессами.
источник
perf report
кажется, дает мне имена функций с родителями вызова ... (так что это вид перевернутой бабочки)gprof2dot
иperf script
. Очень хороший инструмент!perf
в архиве :Я бы использовал Valgrind и Callgrind в качестве основы для своего набора инструментов профилирования. Важно знать, что Valgrind - это виртуальная машина:
Callgrind - это профилировщик, основанный на этом. Основным преимуществом является то, что вам не нужно запускать приложение в течение нескольких часов, чтобы получить надежный результат. Даже одной секунды достаточно для получения надежных и надежных результатов, потому что Callgrind - не зондирующий профилировщик.
Другой инструмент, основанный на Вальгринде, - Массив. Я использую его для профилирования использования памяти кучи. Это прекрасно работает. Что он делает, так это то, что он дает вам снимки использования памяти - детальная информация, ЧТО содержит какой процент памяти, и ВОЗ поместила его туда. Такая информация доступна в разные моменты времени запуска приложения.
источник
Ответ на запуск
valgrind --tool=callgrind
не совсем полный без некоторых вариантов. Обычно мы не хотим профилировать 10 минут медленного запуска в Valgrind и хотим профилировать нашу программу, когда она выполняет какую-то задачу.Так что это то, что я рекомендую. Сначала запустите программу:
Теперь, когда это работает, и мы хотим начать профилирование, мы должны запустить в другом окне:
Это включает профилирование. Чтобы выключить и остановить всю задачу, мы можем использовать:
Теперь у нас есть несколько файлов с именем callgrind.out. * В текущем каталоге. Чтобы увидеть результаты профилирования, используйте:
Я рекомендую в следующем окне нажать на заголовок столбца «Self», иначе он показывает, что «main ()» является наиболее трудоемкой задачей. «Self» показывает, сколько каждой функции потребовалось время, а не вместе с зависимыми.
источник
CALLGRIND_TOGGLE_COLLECT
возможность включать / отключать сбор программно; см. stackoverflow.com/a/13700817/288875Это ответ на ответ Назгоба от Gprof .
Я использовал Gprof последние пару дней и уже нашел три существенных ограничения, одно из которых я не видел нигде (пока), документированных:
Он не работает должным образом для многопоточного кода, если вы не используете обходной путь
Граф вызовов запутывается указателями на функции. Пример: у меня есть вызываемая функция,
multithread()
которая позволяет мне выполнять многопоточность указанной функции по указанному массиву (оба передаются в качестве аргументов). Однако Gprof рассматривает все вызовыmultithread()
как эквивалентные в целях вычисления времени, проведенного у детей. Поскольку некоторые функции, которые я передаю,multithread()
занимают намного больше времени, чем другие, мои графы вызовов в основном бесполезны. (Для тех, кто интересуется, является ли здесь проблема с многопоточностью: нет,multithread()
может, необязательно, и в этом случае выполнил все последовательно только в вызывающем потоке).Он говорит здесь , что «... цифры ЧИСЛО-вызовов получены путем подсчета, не пробуя. Они абсолютно точны ...». Тем не менее, я нахожу свой график вызовов, показывающий мне 5345859132 + 784984078 как статистику вызовов для моей наиболее вызываемой функции, где первый номер должен быть прямым вызовом, а второй рекурсивный вызов (который все из себя). Поскольку это означало, что у меня была ошибка, я вставил в код длинные (64-битные) счетчики и повторил тот же прогон. Мои счета: 5345859132 прямых и 78094395406 саморекурсивных вызовов. Там много цифр, поэтому я укажу, что измеряемые мной рекурсивные вызовы составляют 78 млрд. Против 784 млн. Из Gprof: коэффициент в 100 разнится. Оба прогона были однопотоковыми и неоптимизированными, один компилировался,
-g
а другой-pg
.Это был GNU Gprof (GNU Binutils для Debian) 2.18.0.20080103, работающий под 64-битным Debian Lenny, если это кому-нибудь поможет.
источник
Используйте Valgrind, callgrind и kcachegrind:
генерирует callgrind.out.x. Прочитайте это, используя kcachegrind.
Используйте gprof (добавьте -pg):
(не очень хорошо для многопоточности, указателей на функции)
Используйте google-perftools:
Использует временную выборку, выявляются узкие места ввода-вывода и ЦП.
Intel VTune является лучшим (бесплатно для образовательных целей).
Другие: AMD Codeanalyst (с заменой на AMD CodeXL), OProfile, инструменты 'perf' (apt-get install linux-tools)
источник
Обзор методов профилирования C ++
В этом ответе я буду использовать несколько различных инструментов для анализа нескольких очень простых тестовых программ, чтобы конкретно сравнить, как эти инструменты работают.
Следующая тестовая программа очень проста и выполняет следующие действия:
main
звонкиfast
иmaybe_slow
3 раза, один изmaybe_slow
звонков медленныйМедленный вызов в
maybe_slow
10 раз длиннее и доминирует во время выполнения, если мы рассмотрим вызовы дочерней функцииcommon
. В идеале, инструмент профилирования сможет указать нам на конкретный медленный вызов.оба
fast
иmaybe_slow
вызовcommon
, который составляет основную часть выполнения программыИнтерфейс программы:
и программа делает
O(n^2)
петли в общей сложности.seed
просто получить другой вывод, не влияя на время выполнения.main.c
дргоЕ
gprof требует перекомпиляции программного обеспечения с использованием инструментов, а также использует подход выборки вместе с этим оборудованием. Поэтому он обеспечивает баланс между точностью (выборка не всегда полностью точна и может пропускать функции) и замедлением выполнения (инструментарий и выборка являются относительно быстрыми методами, которые не сильно замедляют выполнение).
gprof встроен в GCC / binutils, поэтому все, что нам нужно сделать, это скомпилировать с
-pg
опцией включения gprof. Затем мы обычно запускаем программу с параметром CLI размера, который производит разумную продолжительность в несколько секунд (10000
):По образовательным причинам мы также проведем прогон без включенной оптимизации. Обратите внимание, что на практике это бесполезно, так как вы обычно заботитесь только об оптимизации производительности оптимизированной программы:
Во-первых,
time
говорит нам, что время выполнения с и без-pg
было одинаковым, и это здорово: никакого замедления! Однако я видел сообщения о 2x - 3x замедлениях на сложном программном обеспечении, например, как показано в этом билете .Поскольку мы скомпилировали, при
-pg
запуске программы создаетсяgmon.out
файл с данными профилирования.Мы можем наблюдать этот файл графически с помощью
gprof2dot
вопроса: можно ли получить графическое представление результатов gprof?Здесь
gprof
инструмент считываетgmon.out
информацию о трассировке и создает отчет, читаемый человекомmain.gprof
, которыйgprof2dot
затем считывает для создания графика.Источник для gprof2dot находится по адресу: https://github.com/jrfonseca/gprof2dot
Мы наблюдаем следующее для
-O0
бега:и для
-O3
бега:-O0
Выход в значительной степени сам за себя. Например, это показывает, что 3maybe_slow
вызова и их дочерние вызовы занимают 97,56% общего времени выполнения, хотя выполнение самогоmaybe_slow
себя без дочерних элементов составляет 0,00% общего времени выполнения, то есть почти все время, потраченное на эту функцию, было потрачено на ребенок звонит.TODO: почему
main
отсутствует в-O3
выводе, хотя я могу видеть егоbt
в GDB? Пропущенная функция из вывода GProf Я думаю, это потому, что gprof также выполняет выборку в дополнение к скомпилированному инструментарию, и-O3
main
она слишком быстра и не имеет выборок.Я выбираю вывод SVG вместо PNG, потому что SVG доступен для поиска с помощью Ctrl + F, а размер файла может быть примерно в 10 раз меньше. Кроме того, ширина и высота сгенерированного изображения могут быть огромными с десятками тысяч пикселей для сложного программного обеспечения, и GNOME
eog
3.28.1 в этом случае работает с ошибками для PNG, тогда как SVG автоматически открываются моим браузером. GIMP 2.8 работал хорошо, см. также:но даже тогда вы будете перетаскивать изображение вокруг, чтобы найти то, что вам нужно, например, посмотрите это изображение из «реального» примера программного обеспечения, взятого из этого билета :
Можете ли вы легко найти наиболее критичный стек вызовов со всеми этими крошечными несортированными линиями спагетти, идущими друг на друга?
dot
Я уверен, что могут быть лучшие варианты, но я не хочу идти туда сейчас. Что нам действительно нужно, так это соответствующий специальный просмотрщик, но я еще не нашел его:Однако вы можете использовать цветовую карту, чтобы немного смягчить эти проблемы. Например, на предыдущем огромном изображении мне наконец-то удалось найти критический путь слева, когда я сделал блестящий вывод, что зеленый цвет идет после красного, а затем, наконец, более темный и темно-синий.
В качестве альтернативы, мы также можем наблюдать вывод текста
gprof
встроенного инструмента binutils, который мы ранее сохранили в:По умолчанию это приводит к чрезвычайно подробному выводу, который объясняет, что означают выходные данные. Так как я не могу объяснить лучше, я позволю вам прочитать это самостоятельно.
Как только вы поняли формат вывода данных, вы можете уменьшить детализацию, чтобы показывать только данные без учебника, с
-b
опцией:В нашем примере выходные данные были для
-O0
:и для
-O3
:Как очень краткое резюме для каждого раздела, например:
центрируется вокруг функции с отступом (
maybe_flow
).[3]
это идентификатор этой функции. Над функцией находятся ее вызывающие, а ниже - вызываемые.Для
-O3
, смотрите здесь, как в графическом выводе, чтоmaybe_slow
иfast
не имеют известного родителя, что означает то, что говорит документация<spontaneous>
.Я не уверен, есть ли хороший способ выполнить построчное профилирование с помощью gprof: `gprof` время, потраченное на определенные строки кода
Valgrind Callgrind
valgrind запускает программу через виртуальную машину valgrind. Это делает профилирование очень точным, но также вызывает очень большое замедление программы. Ранее я также упоминал kcachegrind по адресу: Инструменты для получения графического графического вызова функции.
callgrind - это инструмент valgrind для профилирования кода, а kcachegrind - это программа KDE, которая может визуализировать вывод cachegrind.
Сначала мы должны убрать
-pg
флаг, чтобы вернуться к нормальной компиляции, в противном случае запуск фактически завершится неудачноProfiling timer expired
, и да, это так часто, что я сделал, и для него возник вопрос переполнения стека.Итак, мы компилируем и запускаем как:
Я включаю,
--dump-instr=yes --collect-jumps=yes
потому что это также выводит информацию, которая позволяет нам просматривать разбивку производительности по конвейеру при относительно небольших дополнительных накладных расходах.Необычно,
time
говорит нам, что выполнение программы заняло 29,5 секунды, поэтому у нас было замедление примерно в 15 раз в этом примере. Очевидно, что это замедление станет серьезным ограничением для больших рабочих нагрузок. На упомянутом здесь «примере программного обеспечения реального мира» я наблюдал замедление в 80 раз.Прогон генерирует файл данных профиля с именем,
callgrind.out.<pid>
например,callgrind.out.8554
в моем случае. Мы просматриваем этот файл с:который показывает GUI, который содержит данные, аналогичные текстовому выводу gprof:
Кроме того, если мы перейдем в правую нижнюю вкладку «График вызовов», мы увидим график вызовов, который можно экспортировать, щелкнув его правой кнопкой мыши, чтобы получить следующее изображение с необоснованным количеством белой границы :-)
Я думаю, что
fast
не отображается на этом графике, потому что kcachegrind, должно быть, упростил визуализацию, потому что этот вызов занимает слишком мало времени, это, вероятно, будет поведение, которое вы хотите в реальной программе. Меню правого клика имеет некоторые настройки, чтобы контролировать, когда отбирать такие узлы, но я не смог заставить его показать такой короткий вызов после быстрой попытки. Если я нажимаю наfast
левое окно, оно отображает граф вызововfast
, так что стек фактически был захвачен. Никто еще не нашел способ показать полный график вызовов графа: заставить callgrind показывать все вызовы функций в графе вызовов kcachegrindВ TODO на сложном программном обеспечении C ++ я вижу некоторые записи типа
<cycle N>
, например,<cycle 11>
где я ожидал бы имена функций, что это значит? Я заметил, что есть кнопка «Обнаружение цикла» для включения и выключения, но что это значит?perf
отlinux-tools
perf
похоже, использует исключительно механизмы выборки ядра Linux. Это делает его очень простым в настройке, но также не полностью точным.Это добавило 0,2 с к исполнению, поэтому у нас все хорошо, но я все еще не вижу особого интереса после расширения
common
узла с помощью стрелки вправо на клавиатуре:Затем я пытаюсь сравнить
-O0
программу, чтобы увидеть, показывает ли это что-нибудь, и только теперь, наконец, я вижу график вызовов:ТОДО: что случилось на
-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
:но в такой простой программы на выходе не очень легко понять, так как мы не можем легко увидеть ни
maybe_slow
ниfast
на этом графике:На более сложном примере становится ясно, что означает график:
TODO есть журнал
[unknown]
функций в этом примере, почему это?Другие интерфейсы perf GUI, которые могут стоить того, включают:
Плагин Eclipse Trace Compass: https://www.eclipse.org/tracecompass/
Но у этого есть недостаток, что вы должны сначала преобразовать данные в общий формат трассировки, что можно сделать с помощью
perf data --to-ctf
, но его нужно включить во время сборки / иметьperf
достаточно новый, что не подходит для перфорирования. Ubuntu 18.04https://github.com/KDAB/hotspot
Недостатком этого является то, что, похоже, нет пакета Ubuntu, и для его сборки требуется Qt 5.10, тогда как Ubuntu 18.04 находится на Qt 5.9.
gperftools
Ранее назывался "Инструменты Google Performance", источник: https://github.com/gperftools/gperftools Пример на основе.
Сначала установите gperftools с помощью:
Затем мы можем включить профилировщик ЦП gperftools двумя способами: во время выполнения или во время сборки.
Во время выполнения мы должны передать set
LD_PRELOAD
to pointlibprofiler.so
, который вы можете найтиlocate libprofiler.so
, например, в моей системе:В качестве альтернативы, мы можем встроить библиотеку во время соединения, распределяя передачу
LD_PRELOAD
во время выполнения:Смотрите также: gperftools - файл профиля не выгружен
Самый хороший способ просмотреть эти данные, которые я нашел до сих пор, это сделать вывод pprof таким же форматом, который принимает kcachegrind в качестве ввода (да, Valgrind-project-viewer-tool) и использовать kcachegrind для просмотра этого:
После запуска любого из этих методов мы получаем
prof.out
файл данных профиля в качестве вывода. Мы можем просмотреть этот файл графически как SVG с:который дает знакомый график вызовов, как и другие инструменты, но с неуклюжей единицей количества выборок, а не секунд.
Кроме того, мы также можем получить некоторые текстовые данные с:
который дает:
Смотрите также: Как использовать 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.
источник
-fno-omit-frame-pointer
флагом или использование другой альтернативы: запись с--call-graph "dwarf"
или в--call-graph "lbr"
зависимости от вашего сценария.Для однопоточных программ вы можете использовать igprof , Ignominous Profiler: https://igprof.org/ .
Это профилировщик выборки, аналогичный ответу ... long ... Mike Dunlavey, который будет обернуть результаты в дерево стека вызовов с возможностью просмотра, снабженное комментариями с указанием времени или памяти, затраченных на каждую функцию, либо нарастающую, либо за функции.
источник
Также стоит упомянуть
Я использовал HPCToolkit и VTune, и они очень эффективны при поиске длинного полюса в палатке и не нуждаются в перекомпиляции вашего кода (за исключением того, что вы должны использовать -g -O или RelWithDebInfo type build в CMake, чтобы получить значимый вывод) , Я слышал, что TAU схожи по возможностям.
источник
Вот два метода, которые я использую для ускорения моего кода:
Для приложений, связанных с процессором:
Для приложений ввода-вывода:
NB
Если у вас нет профилировщика, используйте профилировщик для бедняка. Хит пауза при отладке вашего приложения. Большинство комплектов разработчика будут разбиты на сборки с закомментированными номерами строк. По статистике вы можете приземлиться в регионе, который потребляет большую часть ваших циклов ЦП.
Для CPU причина профилирования в режиме DEBUG заключается в том, что, если вы попробовали профилирование в режиме RELEASE , компилятор собирается уменьшить математические, векторизованные циклы и встроенные функции, которые имеют тенденцию превращать ваш код в не отображаемый беспорядок при его сборке. Непоправимый беспорядок означает, что ваш профилировщик не сможет четко определить, что занимает так много времени, поскольку сборка может не соответствовать оптимизируемому исходному коду . Если вам нужна производительность (например, чувствительная ко времени) в режиме RELEASE , отключите функции отладчика, чтобы сохранить работоспособность.
Для привязки к вводу / выводу профилировщик все еще может идентифицировать операции ввода / вывода в режиме RELEASE, потому что операции ввода / вывода либо внешне связаны с общей библиотекой (большую часть времени), либо в худшем случае приведут к системной вектор прерывания вызова (который также легко определяется профилировщиком).
источник
Вы можете использовать библиотеку iprof:
https://gitlab.com/Neurochrom/iprof
https://github.com/Neurochrom/iprof
Он кроссплатформенный и позволяет не измерять производительность вашего приложения также в режиме реального времени. Вы можете даже соединить это с живым графиком. Полный отказ от ответственности: я автор.
источник
Вы можете использовать каркас журналирования, например,
loguru
так как он включает метки времени и общее время безотказной работы, которые можно использовать для профилирования:источник
На работе у нас есть действительно хороший инструмент, который помогает нам отслеживать то, что мы хотим с точки зрения планирования. Это было полезно много раз.
Он находится на C ++ и должен быть настроен в соответствии с вашими потребностями. К сожалению, я не могу поделиться кодом, только понятиями. Вы используете «большой»
volatile
буфер, содержащий метки времени и идентификатор события, которые вы можете выгрузить после вскрытия или после остановки системы ведения журнала (и, например, выгрузить ее в файл).Вы получаете так называемый большой буфер со всеми данными, а небольшой интерфейс анализирует его и показывает события с именем (вверх / вниз + значение), как осциллограф с цветами (настраивается в
.hpp
файле).Вы настраиваете количество генерируемых событий, чтобы сосредоточиться исключительно на том, что вы хотите. Это очень помогло нам в планировании проблем при использовании требуемого количества ЦП в зависимости от количества зарегистрированных событий в секунду.
Вам нужно 3 файла:
Концепция состоит в том, чтобы определять события
tool_events_id.hpp
следующим образом:Вы также определяете несколько функций в
toolname.hpp
:Везде, где в вашем коде вы можете использовать:
probe
Функция использует несколько сборочных линий , чтобы получить тактовую метку времени ASAP , а затем устанавливает запись в буфер. У нас также есть атомарный инкремент для безопасного поиска индекса, в котором будет храниться событие журнала. Конечно буфер является круглым.Надеюсь, что идея не запутана отсутствием примера кода.
источник
На самом деле немного удивлен, что многие не упоминали о google / benchmark , хотя немного сложно связать определенную область кода, особенно если база кода немного большая, однако я нашел это действительно полезным при использовании в сочетании с
callgrind
ИМХО, идентификация части, которая вызывает узкое место, является ключом здесь. Однако сначала я постараюсь ответить на следующие вопросы и выбрать инструмент, основанный на этом.
valgrind
с комбинациейcallrind
иkcachegrind
должен обеспечить достойную оценку по пунктам выше, и как только будет установлено, что есть проблемы с каким-то разделом кода, я бы посоветовал сделать микропробную оценкуgoogle benchmark
- это хорошее место для начала.источник
Используйте
-pg
флаг при компиляции и компоновке кода и запустите исполняемый файл. Во время выполнения этой программы данные профилирования собираются в файле a.out.Существует два разных типа профилирования
1 - Плоское профилирование:
выполнив команду,
gprog --flat-profile a.out
вы получите следующие данные- какой процент от общего времени было потрачено на функцию,
- сколько секунд было потрачено на функцию, включая и исключая вызовы подфункций,
- количество звонки,
- среднее время звонка.
2- представьте
нам команду
gprof --graph a.out
для получения следующих данных для каждой функции, которая включает:- В каждом разделе одна функция помечена индексным номером.
- Выше функции есть список функций, которые вызывают функцию.
- Ниже функции есть список функций, которые вызываются этой функцией.
Чтобы получить больше информации вы можете посмотреть в https://sourceware.org/binutils/docs-2.32/gprof/
источник
Поскольку никто не упомянул Arm MAP, я бы добавил, что лично я успешно использовал Map для профилирования научной программы на C ++.
Arm MAP - это профилировщик для параллельных, многопоточных или однопоточных кодов C, C ++, Fortran и F90. Он обеспечивает углубленный анализ и точное определение узкого места в исходной строке. В отличие от большинства профилировщиков, он предназначен для профилирования pthreads, OpenMP или MPI для параллельного и многопоточного кода.
MAP это коммерческое программное обеспечение.
источник
использовать программное обеспечение для отладки, как определить, где код работает медленно?
просто думайте, что у вас есть препятствие, пока вы находитесь в движении, тогда это снизит вашу скорость
подобно тому, как циклы нежелательного перераспределения, переполнения буфера, поиск, утечки памяти и т. д. операции потребляют большую мощность выполнения, что отрицательно скажется на производительности кода, обязательно добавьте -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 или инструмент отладчика, чтобы прочитать этот файл. Это даст вам графический анализ вещей с результатами, например, какие строки стоят сколько.
я думаю так
источник