Я разрабатываю плагин для стороннего хост-приложения на OSX, используя C ++. Он скомпилирован как .dylib. Я хочу профилировать мой плагин, поскольку он работает в хост-приложении.
К сожалению, хост вызывает код плагина со скоростью, которая варьируется в зависимости от (последнего) времени исполнения плагина. Это означает, что общее время процесса может значительно отличаться от реального времени. Поэтому с профилировщиком выборки «время, потраченное» внутри плагина, на самом деле не основывается на чем-либо полезном, поскольку оно сравнивается только с кадрами стека, которые попадают в процесс. Если я улучшу производительность плагина, то схема исполнения плагина на хосте изменится соответствующим образом, и будет очень сложно измерить улучшения внутри плагина.
Я могу использовать инструменты, но, насколько я могу судить, я могу получить только относительное время по сравнению с временем процессора.
Я использовал dtrace для получения пользовательской гистограммы стека хост-процесса:
#!/usr/sbin/dtrace -s
#pragma ustackframes 100
#pragma D option quiet
/* $1 is pid */
/* $2 is sample rate in Hz (e.g. 100) */
/* $3 is duration (e.g. '20s') */
profile-$2
/pid == $1 && arg1/
{
@[ustack()] = count();
}
tick-$3
{
exit(0);
}
Это работает, но все же предоставляет только выборки относительно времени процесса, поскольку предикат сопоставляется только тогда, когда процесс находится в пространстве пользователя. Даже удаляя && arg1
условие для его запуска во время вызовов ядра процесса не очень помогает.
То, что я действительно хочу знать, это сколько profile-n
образцы привели к тому, что процесс вообще не запущен. Затем я могу сравнить число в моем плагине с общим количеством образцов и получить абсолютный примеры значений для функций моего плагина. Это заставляет меня задуматься — можно ли предположить, что запрошенный profile-n
Частота дискретизации Могу ли я просто взять частоту дискретизации time * и использовать ее для вычисления времени «вне процесса»? Я предполагал, что, скажем, на 1500 Гц, он сбрасывал сэмплы и работал с некоторой другой, неизвестной частотой, но если я могу быть уверен, что это сэмплирование на 1500 Гц, тогда я могу рассчитать время «вне процесса» из этого.
Альтернативно, есть ли известный способ сделать настенные часы профилирование с помощью dtrace?
Это заставляет меня задуматься — можно ли предположить, что запрошенный
profile-n
Частота дискретизации
В Solaris это не гарантируется: некоторым старым аппаратным средствам не хватает необходимой поддержки для прерываний на основе таймера произвольного разрешения. Я бы предположил, что такое же теоретическое ограничение относится к DTrace OS X.
В любом случае вы можете самостоятельно проверить разрешение таймера. документация для провайдера профиля включает в себя соответствующий скрипт и имеет немного больше по теме. Вот еще один скрипт для решения вашего конкретного вопроса:
bash-3.2# cat test.d
uint64_t last;
profile-1500
/cpu == 0/
{
now = timestamp;
@ = lquantize(now - last, 500000, 800000, 30000);
last = now;
}
tick-1
/i++ == 10/
{
exit(0);
}
bash-3.2# dtrace -qs test.dvalue ------------- Distribution ------------- count
560000 | 0
590000 |@@@ 1041
620000 |@@@@@@@@@@ 4288
650000 |@@@@@@@@@@@@@@ 5680
680000 |@@@@@@@@@@ 3999
710000 |@@@@ 1451
740000 | 0
770000 | 0
>= 800000 | 1
bash-3.2#
Обратите внимание, что на практике вы должны производить выборку с частотой, которая является простым числом: это предотвращает синхронизацию с другими, регулярно запланированными, действиями системы.
После обсуждения в комментариях, вот как вы можете измерить затраченное время внутри данной функции:
pid$target:mylib:myfunc:entry
/!self->depth/
{
self->depth = ustackdepth; /* beware recursion */
self->start_time = timestamp; /* for relative wall time calculations */
self->start_vtime = vtimestamp; /* CPU time */
}
pid$target:mylib:myfunc:return
/ustackdepth == self->depth/
{
printf("%d ms (real) %d ms (CPU)\n",
(timestamp - self->start_time) / 1000000,
(vtimestamp - self->start_vtime) / 1000000);
self->depth = 0;
}
Если функция вызывается с высокой частотой, то, очевидно, вы можете поддерживать агрегации прошедших времен, например, рассчитать среднюю стоимость функции.
Вполне возможно выполнить аналогичное упражнение для всех функций в вашей библиотеке, хотя это может быть довольно обременительной задачей — исключить ложные результаты из рекурсии и оптимизации хвостовых вызовов. Чтобы быть более полезным, вы, вероятно, также захотите исключить из стоимости функции время, потраченное на вызов стека; это делает работу еще тяжелее (но не невозможной). Таким образом, вооружившись вышеуказанными средствами для создания объективного ориентира, я был бы более склонен упорствовать с подходом профилирования, возможно, что-то вроде
# cat sample.d
profile-997
/pid == $target && arg1 >= $1 && arg1 < $2/
{
@[ufunc(arg1)] = count();
}
END
{
trunc(@,5);
exit(0);
}
#
Это захватывает пять наиболее часто встречающихся функций в данной области памяти. Например (и используя pmap
на солярисе найти libc
),
# dtrace -qs sample.d -p `pgrep -n firefox` 0xfc090000 0xfc200000
^C
libc.so.1`mutex_lock_impl 35
libc.so.1`clear_lockbyte 46
libc.so.1`gettimeofday 71
libc.so.1`memset 73
libc.so.1`memcpy 170
#
Это оказывается довольно хорошей иллюстрацией преимущества выборки: memcpy()
а также memset()
закодированы вручную в сборке — то есть мы обнаруживаем, что наиболее трудоемкие функции уже оптимизированы.
Других решений пока нет …