Захват времени выхода функции с помощью __gnu_mcount_nc

Я пытаюсь выполнить профилирование производительности на плохо поддерживаемой прототипе встроенной платформы.

Я отмечаю, что флаг GCC -pg заставляет thunks __gnu_mcount_nc вставляться при входе в каждую функцию. Реализация __gnu_mcount_nc недоступна (и поставщик не заинтересован в помощи), однако, поскольку тривиально написать тот, который просто записывает кадр стека и текущий цикл цикла, я сделал это; это отлично работает и дает полезные результаты с точки зрения графиков звонящего/вызываемого абонента и наиболее часто называемых функций.

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

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

Я видел некоторые существующие реализации, которые выполняют такие вещи, как сохранение теневого вызова и сворачивание обратного адреса при входе в __gnu_mcount_nc, так что __gnu_mcount_nc снова будет вызван, когда вызываемый возвращает; он может затем сопоставить триллелет caller/callee/sp с верхней частью теневого вызова и таким образом отличить этот случай от записи вызова, записать время выхода и правильно вернуться к вызывающему.

Этот подход оставляет желать лучшего:

  • похоже, что он может быть хрупким при наличии рекурсии и библиотек, скомпилированных без флага -pg
  • кажется, что было бы сложно реализовать с небольшими накладными расходами или вообще во встроенных многопоточных/многоядерных средах, где поддержка TLS на основе инструментария отсутствует, а текущий идентификатор потока может быть дорогостоящим/сложным, чтобы получить

Есть ли какой-то очевидный лучший способ реализовать __gnu_mcount_nc, чтобы сборка -pg могла захватывать выход функции, а также время входа, которое мне не хватает?

Ответ 1

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

Например, если A называет C 10 раз, а B называет C 20 раз, а C имеет 1000 мс собственного времени (т.е. 100 образцов ПК), тогда gprof знает, что C вызывается 30 раз, а 33 из образцов могут заряжаться до А, в то время как другие 67 могут быть заряжены до Б. Аналогично, количество экземпляров распространяется по иерархии вызовов.

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

Другим подходом является выборка в стеке, а не выборка ПК. Конечно, дороже захватывать образец стека, чем образец ПК, но требуется меньшее количество выборок. Если, например, функция, строка кода или любое описание, которое вы хотите сделать, видно на фракции F из общего количества N выборок, то вы знаете, что доля времени, в течение которого она стоит, равна F со стандартным отклонением of sqrt (NF (1-F)). Так, например, если вы берете 100 выборок, а строка кода отображается на 50 из них, то вы можете оценить стоимость линии в 50% случаев с неопределенностью sqrt (100 *.5 *.5) = +/- 5 образцов или от 45% до 55%. Если вы берете в 100 раз больше образцов, вы можете уменьшить неопределенность в 10 раз. (Рекурсия не имеет значения. Если функция или строка кода появляются 3 раза в одном образце, это считается как 1 образец, а не 3. Также не имеет значения, являются ли вызовы функций короткими - если их называют достаточно временными, чтобы стоить значительную долю, они будут пойманы.)

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

Этот этот метод.


P.S. Не присоединяйтесь к графам вызовов, горячим путям или горячим точкам. Здесь типичное гнездо крысы-графа. Желтый - это горячая дорожка, а красный - горячая точка.

enter image description here

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

enter image description here

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

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

  • 3/10 образцов вызывают class_exists, один для получения имени класса, и два для настройки локальной конфигурации. class_exists вызывает autoload, который вызывает requireFile, а два из них вызывают adminpanel. Если это можно сделать более прямо, это может сэкономить около 30%.
  • 2/10 сэмплы вызывают determineId, который вызывает fetch_the_id, который вызывает getPageAndRootlineWithDomain, который вызывает еще три уровня, заканчивающихся на sql_fetch_assoc. Кажется, что у вас много проблем, чтобы получить идентификатор, и это стоит около 20% времени, и что не считая ввода/вывода.

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

ДОБАВЛЕНО: Еще одна вещь, которую нельзя всасывать, - это графики пламени. Например, здесь приведен график графа пламени (повернутый на 90 градусов) из десяти смоделированных выборок стека из графика вызовов выше. Все подпрограммы нумеруются, а не называются, но каждая процедура имеет свой собственный цвет. enter image description here
Обратите внимание, что проблема, указанная выше, с class_exists (процедура 219), которая находится на 30% образцов, совершенно не очевидна, глядя на график пламени. Больше образцов и разных цветов сделают график более "пламенным", но не выставляют процедуры, которые занимают много времени, вызывая много раз из разных мест.

Здесь одни и те же данные сортируются по функции, а не по времени. Это немного помогает, но не объединяет сходства, вызванные из разных мест: enter image description here
И снова цель состоит в том, чтобы найти проблемы, которые скрываются от вас. Любой может найти легкий материал, но проблемы, которые скрываются, - это те, которые имеют значение.

ДОБАВЛЕНО: Еще один вид глазной конфеты - это:
enter image description here где чернокожие подпрограммы могут быть одинаковыми, просто вызванными из разных мест. Диаграмма не сводит их для вас. Если процедура имеет высокий инклюзивный процент, вызывая большое количество раз из разных мест, она не будет отображаться.