我正在使用C ++为OSX上的第三方主机应用程序开发插件。它被编译为.dylib。我希望在主机应用程序中运行我的插件。
不幸的是,主机调用插件代码的速率取决于插件的(最后)执行时间。这意味着该过程的总体时间可能相对于实时变化很大。因此,使用采样分析器,插件中的“花费的时间”并没有真正基于任何有用的东西,因为它只与进程内的堆栈帧进行比较。如果我提高了插件的性能,那么主机执行插件的模式会相应地改变,并且很难测量插件中的改进。
我可以使用乐器但据我所知,我只能得到相对时间来反对该过程的CPU时间。
我已经使用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
采样率得到尊重是否安全?我可以简单地花时间*采样率并用它来计算“关闭过程”时间吗?我曾经假设,比如1500Hz,它正在丢弃样本并以其他未知的速率运行,但是如果我可以确定它是以1500Hz采样那么我可以从中计算出“进程外”时间。 / p>
或者,是否有一种已知的方法可以使用dtrace进行挂钟分析?
答案 0 :(得分:1)
这让我想知道 - 假设所要求的
profile-n
采样率得到尊重是否安全?
在Solaris上,它不能保证得到尊重:一些旧硬件缺乏对基于任意分辨率定时器的中断的必要支持。我认为相同的理论限制适用于OS X的DTrace。
在任何情况下,您都可以自己测试计时器分辨率。配置文件提供程序的documentation包含一个适当的脚本,并且有关于该主题的更多信息。这是另一个解决您具体问题的脚本:
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.d
value ------------- 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);
}
#
这捕获了给定内存区域中最常见的五个函数。例如(并在Solaris上使用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()
在汇编中手工编码 - 即我们发现最耗时的函数已经已经过优化。