Время выхода из функции захвата с __gnu_mcount_nc
Я пытаюсь выполнить профилирование производительности на плохо поддерживаемой встроенной платформе прототипа.
Я отмечаю, что флаг -pg GCC приводит к тому, что __gnu_mcount_nc
быть вставленным при входе в каждую функцию. Нет реализации __gnu_mcount_nc
доступен (и поставщик не заинтересован в оказании помощи), однако, так как написать тривиальный кадр стека и текущий счетчик циклов тривиально, я сделал это; это прекрасно работает и дает полезные результаты с точки зрения графов вызывающих / вызываемых и наиболее часто вызываемых функций.
Мне бы очень хотелось получить информацию о времени, проведенном в телах функций, однако мне трудно понять, как подходить к этому, используя только вход, но не выход, чтобы каждая функция была подключена: вы можете точно сказать, когда каждая функция введен, но без перехвата точек выхода вы не можете знать, сколько времени, пока вы не получите следующий фрагмент информации, который нужно связать с вызываемым абонентом и сколько с вызывающим абонентом.
Тем не менее, инструменты профилирования GNU фактически способны накапливать информацию о времени выполнения для функций на многих платформах, поэтому, вероятно, разработчики имеют в виду некоторую схему для достижения этой цели.
Я видел несколько существующих реализаций, которые выполняют такие вещи, как поддержание теневого стека вызовов и изменение адреса возврата при входе в __gnu_mcount_nc, чтобы __gnu_mcount_nc снова вызывался после возвращения вызываемого; затем он может сопоставить триплет вызывающий / вызываемый /sp с вершиной теневого стека вызовов и таким образом отличить этот случай от входящего вызова, записать время выхода и правильно вернуться к вызывающему.
Такой подход оставляет желать лучшего:
- кажется, что он может быть хрупким при наличии рекурсии и библиотек, скомпилированных без флага -pg
- кажется, что это было бы трудно реализовать с низкими издержками или вообще во встроенных многопоточных / многоядерных средах, где поддержка TLS инструментальной цепи отсутствует, и текущий идентификатор потока может быть дорогим / сложным для получения
Есть ли какой-то очевидный лучший способ реализовать __gnu_mcount_nc, чтобы сборка -pg могла фиксировать время выхода из функции и время пропуска?
1 ответ
gprof не использует эту функцию для определения времени, входа или выхода, но для подсчета вызовов функции A, вызывающей любую функцию B. Скорее, он использует собственное время, полученное путем подсчета выборок ПК в каждой подпрограмме, а затем использует функцию Вызов функции to-function подсчитывает, какая часть этого времени должна быть возвращена абонентам.
Например, если A вызывает C 10 раз, а B вызывает C 20 раз, а C имеет 1000 мс собственного времени (т. Е. 100 выборок ПК), то gprof знает, что C был вызван 30 раз, и 33 из выборок могут быть заряжены на A, в то время как остальные 67 могут быть отнесены на счет B. Аналогично, отсчеты выборок распространяются вверх по иерархии вызовов.
Итак, вы видите, это не время входа и выхода из функции. Измерения, которые он получает, очень грубые, потому что он не делает различий между короткими и длинными звонками. Кроме того, если образец ПК происходит во время ввода-вывода или в подпрограмме библиотеки, которая не скомпилирована с параметром -pg, он вообще не учитывается. И, как вы заметили, он очень хрупок при наличии рекурсии и может привести к значительным накладным расходам на короткие функции.
Другой подход - выборка из стека, а не выборка с ПК. Конечно, сбор стекового сэмпла обходится дороже, чем сэмплирование с ПК, но требуется меньшее количество сэмплов. Если, например, функция, строка кода или какое-либо описание, которое вы хотите сделать, видно из дроби F из общего числа N выборок, то вы знаете, что доля времени, которую она стоит, равна F со стандартным отклонением. sqrt(NF(1-F)). Так, например, если вы берете 100 сэмплов, а на 50 из них появляется строка кода, то вы можете оценить стоимость линии в 50% случаев с неопределенностью sqrt(100*.5*.5) = +/- 5 образцов или от 45% до 55%. Если вы берете в 100 раз больше выборок, вы можете уменьшить неопределенность в 10 раз. (Рекурсия не имеет значения. Если функция или строка кода появляются 3 раза в одной выборке, это считается как 1 выборка, а не 3 Также не имеет значения, если вызовы функций короткие - если они вызываются достаточно много раз, чтобы стоить значительной доли, они будут перехвачены.)
Обратите внимание: когда вы ищете вещи, которые можно исправить, чтобы ускорить процесс, точный процент не имеет значения. Важно найти его. (На самом деле, вам нужно только дважды увидеть проблему, чтобы понять, что она достаточно велика, чтобы ее исправить.)
Вот эта техника.
PS Не увлекайтесь графами вызовов, горячими путями или горячими точками. Вот типичное гнездо для крыс-графа. Желтый - это горячая дорога, а красный - горячая точка.
И это показывает, как легко сочной возможностью ускорения оказаться ни в одном из этих мест:
Самая ценная вещь, которую стоит посмотреть, - это дюжина или около того случайных необработанных образцов стека и их связь с исходным кодом. (Это означает обход обхода профилировщика.)
ДОБАВЛЕНО: просто чтобы показать, что я имею в виду, я смоделировал десять образцов стека из приведенного выше графика вызовов, и вот что я нашел
- 3/10 образцы звонят
class_exists
один для получения имени класса, а второй для настройки локальной конфигурации.class_exists
звонкиautoload
какие звонкиrequireFile
и два из них называютadminpanel
, Если это можно сделать более напрямую, это может сэкономить около 30%. - 2/10 образцы звонят
determineId
, который вызываетfetch_the_id
какие звонкиgetPageAndRootlineWithDomain
, который вызывает еще три уровня, оканчивающиеся наsql_fetch_assoc
, Кажется, что получить идентификацию очень сложно, и это стоит около 20% времени, и это не считая ввод-вывод.
Таким образом, примеры стека не просто говорят вам, сколько времени стоит функция или строка кода, но и объясняют, почему это делается, и какую глупость требуется для его выполнения. Я часто вижу это - скачущую общность - отмахивание мухами молотками, не намеренно, а просто следуя хорошему модульному дизайну.
ДОБАВЛЕНО: Еще одна вещь, в которую нельзя попасть - это графики пламени. Например, вот график пламени (повернутый на 90 градусов вправо) из десяти смоделированных образцов стека из приведенного выше графика вызовов. Все подпрограммы нумеруются, а не именуются, но у каждой подпрограммы свой цвет.
Обратите внимание, что проблема, которую мы определили выше, с class_exists (процедура 219) в 30% выборок, совсем не очевидна, если посмотреть на график пламени. Больше образцов и разных цветов сделало бы график более "похожим на пламя", но не выставляло бы подпрограммы, которые занимают много времени, будучи вызванными много раз из разных мест.
Вот те же данные, отсортированные по функциям, а не по времени. Это немного помогает, но не объединяет сходства, вызванные из разных мест:
Еще раз, цель состоит в том, чтобы найти проблемы, которые скрываются от вас. Любой может найти легкий материал, но проблемы, которые скрывают, имеют значение.
ДОБАВЛЕНО: Еще один вид конфеток для глаз:где обведенные черным цветом подпрограммы могут быть одинаковыми, просто вызванными из разных мест. Диаграмма не объединяет их для вас. Если у подпрограммы высокий процент включения, если ее вызывать много раз из разных мест, она не будет раскрыта.