2009年3月26日星期四

提升PP的性能

因为昨天出门很失败,于是今天我试验了一下us级的PP,果然,对深度调用的情况影响很大。

我看了一下代码,主要是因为同步引起的。在进行profile的时候需要进入临界区,VM在处理调用时(进、退调用栈)也会进入临界区,这样就保证了profile的时候不会发生栈变化的情况。

然而,我试验了33层长函数名的调用栈profile的情况,需要17us,锁住临界区如此长的时间,显然有问题。于是我优化了一下这部分的代码:生成调用树主要是依赖于函数的名字,每次根据函数名进行哈希查找成本有些大,于是我改为根据函数名的指针进行哈希查找。但是这样引发了一个新问题,因为脚本是可以动态卸载的,如果函数被释放了怎么办?我采用的方案是另外记录一个mapping,根据函数名指针可以朝着到对应的函数名。每次在记录调用栈中的函数名时,如果这个指针已经存在了,则忽略,否则就先记录这个指针对应的函数名。

实际上在做profile的时候,几乎不会发生动态析构脚本的情况,直接使用函数名指针效率是最高的。但是我并不希望这个profile依赖用户的行为(比如他无意进行了析构,可能就会导致问题)。我这样做仍然有一个微小的隐患,如果用户析构了脚本函数又重新加载了,新的函数名指针恰好和原先的函数名指针相等的话,统计就会记录错误的名字 - 我认为这并不重要。

另外,我增加了一步:复制调用栈,在临界区内我只复制调用栈,然后在临界区外进行生成调用树的操作,这样就尽可能的减少和VM的冲突了。为了防止在生成调用树的时候发生脚本析构的情况,我另外增加了一个生成调用数时的临界区,在析构脚本的时候VM会进入这个临界区,避免发生不同步的情况。

优化以后速度快了20倍,毕竟每次哈希都不需要处理字符串,而是处理一个64位的整数即可(因为除了函数名,我还需要记录对象名,各32位)。另外临界区冲突的可能性则更是大大的降低了。

但是性能仍然让我不够满意,这还是没有达到1us级别的统计性能,只能进行10us级别的统计。因为统计33层调用本身需要0.7us左右,在这种情况下进行1us间隔的统计意义并不大,因为每次采样之间波动可能就会达到1us。

我暂时采用限制PP最低以10us的间隔进行采用,将来再考虑如何进行进一步优化。现在还有一个问题,就是PP以10us的间隔进行采样时,VM性能大概会降低30%-40%,主要是函数调用会受到影响。如果关掉同步自然皆大欢喜,速度立刻恢复正常。但是我始终不愿意这样做,虽然这样只是会让统计结果稍微有些偏差而已。

公司今年5周年庆,不知不觉,已经过去5年了。以目前的成绩来看,和想象还很有差距啊!

没有评论:

发表评论