У меня есть приложение на C++, работающее в Linux, которое я оптимизирую. Как я могу определить, какие области моего кода работают медленно?
Ответ на него уже дан по следующей ссылке: stackoverflow.com/questions/2497211/…
Большинство ответов - профилировщики code. Однако инверсия приоритета, псевдонимы кеша, конкуренция за ресурсы и т. д. Могут быть факторами оптимизации и производительности. Я думаю, что люди читают информацию в мой медленный код. Часто задаваемые вопросы ссылаются на эту ветку.
Раньше я использовал pstack случайным образом, большую часть времени распечатывал наиболее типичный стек, в котором большую часть времени работает программа, следовательно, указывая на узкое место.
cachegrind / callgrind?
Вы смотрели варианты своего компилятора? Ваш компилятор недавний?





Я предполагаю, что вы используете GCC. Стандартное решение - профилировать с помощью гпроф.
Обязательно добавьте -pg в компиляцию перед профилированием:
cc -o myprog myprog.c utils.c -g -pg
Я еще не пробовал, но слышал хорошие отзывы о google-perftools. Однозначно стоит попробовать.
Связанный вопрос здесь.
Несколько других модных словечек, если gprof не выполняет эту работу за вас: Валгринд, Intel VTune, Sun DTrace.
Я согласен с тем, что gprof является действующим стандартом. Замечу, что Valgrind используется для профилирования утечек памяти и других связанных с памятью аспектов ваших программ, а не для оптимизации скорости.
Bill, В vaglrind suite можно найти callgrind и massif. Оба очень полезны для профильных приложений.
@ Bill-the-Lizard: Некоторые комментарии к гпроф: stackoverflow.com/questions/1777556/alternatives-to-gprof/…
gprof -pg - это только приблизительное описание профилирования стека вызовов. Он вставляет вызовы mcount, чтобы отслеживать, какие функции вызывают какие другие функции. Он использует стандартную выборку, основанную на времени, для времени. Затем он распределяет время, выбранное функцией foo (), обратно вызывающим foo (), пропорционально количеству вызовов. Таким образом, он не делает различий между звонками с разной стоимостью.
С clang / clang ++ можно рассмотреть возможность использования профилировщика ЦП gperftools. Предостережение: я не делал этого сам.
это работает, только если ваш код не выделяет много памяти и в то же время не пытается создать новый процесс. В противном случае зависает fork ().
Вы можете использовать Валгринд со следующими параметрами
valgrind --tool=callgrind ./(Your binary)
Будет создан файл с именем callgrind.out.x. Затем вы можете использовать инструмент kcachegrind, чтобы прочитать этот файл. Это даст вам графический анализ вещей с результатами, например, сколько строк стоит.
valgrind великолепен, но имейте в виду, что он сделает вашу программу чертовски медленной
Также ознакомьтесь с Gprof2Dot, чтобы узнать об удивительном альтернативном способе визуализации вывода. ./gprof2dot.py -f callgrind callgrind.out.x | dot -Tsvg -o output.svg
@neves Да, Valgrind не очень помогает с точки зрения скорости профилирования приложений "gstreamer" и "opencv" в реальном времени.
@ Себастьян: gprof2dot теперь здесь: github.com/jrfonseca/gprof2dot
Одна вещь, которую следует иметь в виду, - это скомпилировать С включенными отладочными символами, но С оптимизацией, чтобы получить что-то доступное для исследования, но со скоростными характеристиками, аналогичными фактической «релизной» сборке.
Я бы использовал Valgrind и Callgrind в качестве основы для своего набора инструментов профилирования. Важно знать, что Valgrind - это, по сути, виртуальная машина:
(wikipedia) Valgrind is in essence a virtual machine using just-in-time (JIT) compilation techniques, including dynamic recompilation. Nothing from the original program ever gets run directly on the host processor. Instead, Valgrind first translates the program into a temporary, simpler form called Intermediate Representation (IR), which is a processor-neutral, SSA-based form. After the conversion, a tool (see below) is free to do whatever transformations it would like on the IR, before Valgrind translates the IR back into machine code and lets the host processor run it.
Callgrind - это профилировщик, построенный на этом. Главное преимущество в том, что вам не нужно запускать приложение часами, чтобы получить надежный результат. Даже одной секунды запуска достаточно, чтобы получить твердые и надежные результаты, потому что Callgrind является профилировщиком не исследующий.
Еще один инструмент, созданный на основе Valgrind, - это Massif. Я использую его для профилирования использования памяти кучи. Отлично работает. Что он делает, так это то, что он дает вам снимки использования памяти - подробную информацию, ЧТО занимает КАКОЙ процент памяти, и КТО поместил ее туда. Такая информация доступна в разные моменты времени работы приложения.
Новые ядра (например, последние ядра Ubuntu) поставляются с новыми инструментами perf (apt-get install linux-tools) AKA perf_events.
Они поставляются с классическими профилировщиками выборки (справочная страница), а также потрясающим график!
Важно то, что этими инструментами может быть профилирование системы, а не просто профилирование процессов - они могут показывать взаимодействие между потоками, процессами и ядром и позволять вам понять планирование и зависимости ввода-вывода между процессами.

Отличный инструмент! Могу ли я получить типичное представление «бабочка», которое начинается со стиля «main-> func1-> fun2»? Кажется, я не могу понять этого ... perf report, кажется, дает мне имена функций с родительскими вызовами ... (так что это своего рода перевернутое представление бабочки)
Will, может перфоманс показать временную диаграмму активности потоков; с добавленной информацией о номере процессора? Я хочу видеть, когда и какой поток выполнялся на каждом процессоре.
@ kizzx2 - можно использовать gprof2dot и perf script. Очень красивый инструмент!
Даже более новые ядра, такие как 4.13, имеют eBPF для профилирования. См. brendangregg.com/blog/2015-05-15/ebpf-one-small-step.html и brendangregg.com/ebpf.html.
Еще одно хорошее введение в perf существует на archive.li/9r927#selection-767.126-767.271 (Почему боги SO решили удалить эту страницу из базы знаний SO, мне не понятно ....)
Это должен быть принятый ответ. Использование отладчика приводит к слишком большому шуму в образцах. Счетчики производительности для Linux работают для нескольких потоков, нескольких процессов, пространства пользователя и ядра, и это здорово. Вы также можете получить много полезной информации, такой как промахи веток и кешей. На том же веб-сайте, о котором упоминалось @AndrewStern, есть граф пламени, который очень полезен для такого рода анализа: графики пламени. Он генерирует файлы SVG, которые можно открывать в веб-браузере для интерактивных графиков!
Это ответ на Ответ Назгоба 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, если это кому-то поможет.
Да, выборка выполняется, но не для определения количества звонков. Интересно, что переход по вашей ссылке в конечном итоге привел меня к обновленной версии справочной страницы, на которую я ссылался в своем сообщении, новый URL: sourceware.org/binutils/docs/gprof/… Это повторяет цитату в части (iii) моего ответа, но также говорит: «В многопоточных приложениях, или однопоточные приложения, которые связываются с многопоточными библиотеками, счетчики являются детерминированными только в том случае, если функция счета является потокобезопасной. (Примечание: имейте в виду, что функция счета mcount в glibc не является потокобезопасной) ».
Мне не ясно, объясняет ли это мой результат в (iii). Мой код был связан -lpthread -lm и объявил как статическую переменную «pthread_t * thr», так и «pthread_mutex_t nextLock = PTHREAD_MUTEX_INITIALIZER», даже когда он работал в однопоточном режиме. Обычно я предполагаю, что «связывание с многопоточными библиотеками» означает фактическое использование этих библиотек, и в большей степени, чем это, но я могу ошибаться!
Ответ на вопрос о запуске 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.*
Я рекомендую в следующем окне щелкнуть заголовок столбца «Я», иначе он показывает, что «main ()» - это наиболее трудоемкая задача. «Я» показывает, сколько времени занимала каждая функция, а не иждивенцы.
Теперь почему-то файлы callgrind.out. * Всегда были пустыми. Выполнение callgrind_control -d было полезно для принудительного сброса данных на диск.
я запутался в этих инструкциях. Вы говорите, что пока программа запущена, мы можем запустить callgrind_control в другом окне, чтобы включить / выключить профилирование? Мне кажется, что было бы лучше разработать минимальную программу, включающую только то, что вы хотите профилировать, а затем профилировать всю программу.
Не могу. Мои обычные контексты - это что-то вроде всего MySQL или PHP или чего-то подобного. Часто даже сначала не знаю, что хочу отделить.
Или в моем случае моя программа фактически загружает кучу данных в кеш LRU, и я не хочу профилировать это. Поэтому я принудительно загружаю подмножество кеша при запуске и профилирую код, используя только эти данные (позволяя ОС + ЦП управлять использованием памяти в моем кеше). Он работает, но загрузка этого кеша происходит медленно и интенсивно использует процессор для кода, который я пытаюсь профилировать в другом контексте, поэтому callgrind дает сильно загрязненные результаты.
есть также CALLGRIND_TOGGLE_COLLECT для программного включения / выключения сбора; см. stackoverflow.com/a/13700817/288875
@ TõnuSamuel, у меня тоже callgrind.out. * Был пуст. В моем случае при профилировании программа становилась сумасшедшей. После устранения причины сбоя я могу видеть содержимое файла callgrind.out. *.
Вот два метода, которые я использую для ускорения кода:
Для приложений с привязкой к ЦП:
Для приложений с привязкой к вводу-выводу:
N.B.
Если у вас нет профилировщика, используйте профилировщик бедняков. Нажмите паузу во время отладки вашего приложения. Большинство пакетов разработчика разбиваются на сборку с закомментированными номерами строк. По статистике, вы попадете в регион, который потребляет большую часть циклов вашего процессора.
Для ЦП причина профилирования в режиме ОТЛАЖИВАТЬ заключается в том, что если вы попробуете профилирование в режиме РЕЛИЗ, компилятор будет сокращать математические вычисления, векторизовать циклы и встроенные функции, что имеет тенденцию превращать ваш код в неразрешимый беспорядок при его сборке. Неподписываемый беспорядок означает, что ваш профилировщик не сможет четко определить, что занимает так много времени, потому что сборка может не соответствовать исходному коду при оптимизации.. Если вам нужна производительность (например, чувствительная к времени) режима РЕЛИЗ, отключите функции отладчика по мере необходимости, чтобы поддерживать работоспособную производительность.
Для привязки к вводу-выводу профилировщик все еще может идентифицировать операции ввода-вывода в режиме РЕЛИЗ, потому что операции ввода-вывода либо связаны извне с общей библиотекой (большую часть времени), либо, в худшем случае, приведут к сис- теме вектор прерывания вызова (который также легко идентифицируется профилировщиком).
+1 Метод бедняги работает так же хорошо для ограничений ввода / вывода, как и для CPU, и я рекомендую выполнять всю настройку производительности в режиме DEBUG. Когда вы закончите настройку, включите RELEASE. Улучшение будет, если программа в вашем коде привязана к ЦП. Вот грубое, но короткое видео процесса.
Я бы не стал использовать сборки DEBUG для профилирования производительности. Часто я видел, что критически важные для производительности части в режиме DEBUG полностью оптимизированы в режиме выпуска. Другая проблема - использование утверждений в отладочном коде, которые добавляют шум к производительности.
Вы вообще мой пост читали? «Если вам нужна производительность (например, чувствительная к времени) режима RELEASE, отключите функции отладчика по мере необходимости, чтобы сохранить работоспособную производительность», «Затем переключитесь в режим RELEASE и прокомментируйте сомнительные разделы вашего кода (заглушите его без ничего), пока не увидите изменения в производительности. "? Я сказал, проверьте возможные проблемные области в режиме отладки и проверьте эти проблемы в режиме выпуска, чтобы избежать упомянутой вами ловушки.
Используйте Valgrind, callgrind и kcachegrind:
valgrind --tool=callgrind ./(Your binary)
генерирует callgrind.out.x. Прочтите это с помощью kcachegrind.
Используйте gprof (add -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)
Для однопоточных программ вы можете использовать игпроф, The Ignominous Profiler: https://igprof.org/.
Это профилировщик выборки, аналогичный ... длинному ... ответу Майка Данлэйви, который в подарок обернет результаты в просматриваемое дерево стека вызовов, аннотированное временем или памятью, потраченной на каждую функцию, либо кумулятивно, либо за функцию.
Выглядит интересно, но не компилируется с GCC 9.2. (Debian / Sid) Я сделал ошибку на github.
Также стоит упомянуть
Я использовал HPCToolkit и VTune, и они очень эффективны для поиска длинного столба в палатке и не нуждаются в перекомпиляции кода (за исключением того, что вам нужно использовать сборку типа -g -O или RelWithDebInfo в CMake для получения значимого вывода) . Я слышал, что TAU похожи по возможностям.
Вы можете использовать библиотеку iprof:
https://gitlab.com/Neurochrom/iprof
https://github.com/Neurochrom/iprof
Он кроссплатформенный и позволяет не измерять производительность вашего приложения также в режиме реального времени. Вы даже можете связать это с живым графиком. Полный отказ от ответственности: я являюсь автором.
На работе у нас есть действительно хороший инструмент, который помогает нам отслеживать то, что мы хотим с точки зрения планирования. Это было полезно много раз.
Он написан на 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 использует несколько сборочных линий для получения метки времени часов как можно скорее, а затем устанавливает запись в буфере. У нас также есть атомарное приращение, чтобы безопасно найти индекс, где хранить событие журнала.
Конечно буфер круглый.
Надеюсь, идея не запуталась из-за отсутствия образца кода.
Поскольку никто не упомянул Arm MAP, я бы добавил его, поскольку лично я успешно использовал Map для профилирования научной программы на C++.
Arm MAP - это профилировщик для параллельных, многопоточных или однопоточных кодов C, C++, Fortran и F90. Он обеспечивает углубленный анализ и определение узких мест в строке источника. В отличие от большинства профилировщиков, он предназначен для профилирования потоков pthread, OpenMP или MPI для параллельного и многопоточного кода.
MAP - коммерческое программное обеспечение.
На самом деле немного удивлен, что не многие упомянули о гугл / эталон, в то время как немного громоздко закрепить конкретную область кода, особенно если база кода немного большая, однако я нашел это действительно полезным при использовании в сочетании с callgrind
ИМХО, ключевым моментом здесь является определение того, что вызывает узкое место. Однако я сначала попробую ответить на следующие вопросы и выберу инструмент на основе этого
valgrind с комбинацией callrind и kcachegrind должен дать достойную оценку по вышеперечисленным пунктам, и как только будет установлено, что есть проблемы с некоторым разделом кода, я бы посоветовал сделать микротест google benchmark - хорошее место для начала.
Я обнаружил, что мои результаты тестов Google выглядят более точными, чем gprof, когда я измерял участки кода. Как вы сказали, он действительно хорош для микробенчмаркинга. но если вы хотите получить более целостную картину, вам нужен другой подход.
Используйте флаг -pg при компиляции и компоновке кода и запустите исполняемый файл. Пока эта программа выполняется, данные профилирования собираются в файле a.out.
.
Существует два разных типа профилирования
1- Плоское профилирование:
запустив команду gprog --flat-profile a.out, вы получите следующие данные
- какой процент от общего времени был потрачен на выполнение функции,
- сколько секунд было потрачено на выполнение функции, включая и исключая вызовы подфункций,
- количество звонков,
- среднее время одного звонка.
2- графическое профилирование
используйте команду gprof --graph a.out, чтобы получить следующие данные для каждой функции, которая включает
- В каждом разделе одна функция отмечена порядковым номером.
- Над функцией находится список функций, которые вызывают функцию .
- Ниже функции есть список функций, которые вызываются функцией .
Чтобы получить больше информации, вы можете посмотреть https://sourceware.org/binutils/docs-2.32/gprof/
использовать программное обеспечение для отладки как определить, где код работает медленно?
просто подумайте, что у вас есть препятствие, пока вы в движении, тогда ваша скорость уменьшится
как это нежелательное перераспределение циклов, переполнение буфера, поиск, утечки памяти и т. д. операции потребляют больше мощности выполнения, что отрицательно сказывается на производительности кода, Не забудьте добавить -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 или инструмент отладчика для чтения этого файла. Это даст вам графический анализ вещей с результатами, например, сколько строк стоит.
я думаю так
На самом деле я бы предложил добавить некоторый флаг оптимизации, например. компилировать с g++ -O -pg -Wall your_prg.cpp
Если вы предоставите больше данных о своем стеке разработки, вы получите более точные ответы. Существуют профилировщики от Intel и Sun, но вы должны использовать их компиляторы. Это вариант?