Linux

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

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

Ответ 1

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

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

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

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

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

  1. Они не суммируют данные на уровне инструкций.

  2. Они дают запутанные обобщения в присутствии рекурсии.

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

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

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

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

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

Ответ 2

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

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

Другой инструмент, построенный на основе Valgrind, Massif. Я использую его для профилирования использования памяти кучи. Он отлично работает. Что он делает, так это дает вам снимки использования памяти подробную информацию о том, ЧТО занимает КАКОЙ процент памяти, и КТО ее туда поместил. Такая информация доступна в разные моменты времени работы приложения.

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

  • Затем переключитесь в режим RELEASE и закомментируйте сомнительные участки кода, пока не увидите изменения в производительности.

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

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

  • Если у вас нет профилировщика, используйте «ручной» профилировщик. Нажимайте паузу во время отладки приложения. Большинство пакетов разработчика разбивают ассемблер с закомментированными номерами строк. Статистически вероятно, что вы попадете в область, которая «съедает» большую часть циклов вашего процессора.

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

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

Схожие статьи

Linux

Как объединить две статические библиотеки «ar» в одну

Что такое сетевой сканер Ubuntu, как его правильно настроить
Linux

Что такое сетевой сканер Ubuntu, как его правильно настроить

Linux

Как используется брандмауэр?

Linux

Как узнать, взломан ли мой Linux-сервер?