跟踪器(Trace)功能是谷歌Go语言中一个用来排除故障的实用工具。该功能可以让开发人员在一段时间内生成每个goroutine执行的跟踪,从而展示程序中难以通过其他方式看到的信息,快速排查问题。但长期以来跟踪器过大的开销一直饱受诟病,它会消耗10-20%的CPU资源,具体取决于不同的应用场景。
而在近日,谷歌Go语言开发团队发布消息称,改进了其运行环境的跟踪器功能,可解决跟踪器开销过大的问题,将CPU负载降低至1%。
栈展开是获取堆栈跟踪过程的一部分,它涉及迭代所有堆栈帧并收集每个帧中的返回地址(程序计数器),如果某些程序计数器是内联函数调用的一部分,则还可能涉及扩展此列表。获取堆栈跟踪的另一部分是将这些程序计数器转换为函数名、文件名和行号,这通常被称为符号化。而Go语言会使用一种名为gopclntab的异步展开表形式来实现跟踪器功能,这种栈展开方式需要消耗很大的CPU资源查找才能遍历堆栈帧,这也是跟踪器开销过大的罪魁祸首。
为了解决这一问题,Go语言开发团队使用了帧指针技术来优化跟踪器,这种技术已经存在了几十年,但是跟踪器功能是在go1.5版本中添加的,而Go编译器直到go1.7版本才加入了帧指针指令。
通过帧指针,程序可以在运行时准确地访问和管理函数的局部数据,使用更低的开销遍历堆栈帧,最终成功地将跟踪器开销降低至CPU负载的1%。
使用帧指针展开遍历堆栈
事实上,在编译器的发展历史上,帧指针由于性能原因曾被很多编译器禁用,但其对于目前寄存器非常有限的32位CPU来说其实是很有意义的,人们现在也正在重新评估这项技术,考虑在Linux上为C/C++应用启用帧指针。