本文介绍如何运用性能监控(火焰图等)工具,分析应用程序的性能数据以及实现应用程序的CPU、内存优化。
背景信息
日志服务性能监控平台为开发者提供开放、一站式性能持续分析功能,目前已完全兼容Pyroscope开源SDK和Agent。
本实践基于A公司的降本增效需求(要求上半年微服务接口性能至少提升30%),介绍日志服务性能监控平台在提升应用程序CPU、内存性能上的应用。A公司待性能优化的为一个gRPC服务,在限定资源情况下(CPU限制为250m核,内存限制为50MB),使用日志方式打印了压测下此服务待优化接口的性能,发现QPS仅仅在30左右。
CPU优化
定位问题
A公司将性能数据接入到日志服务的性能监控平台后,发现有一个内部库函数regexp.MatchString消耗了应用程序的大量CPU性能。
分析问题
此应用程序的业务逻辑为通过regexp.MatchString函数计算存储文件行所包含的输入字符串数量。实际上,此业务未涉及复杂的正则提取,通过字符串包含操作即可满足业务需求。相比于正则匹配,字符串匹配的算法复杂度更低,优化如下所示。
性能对比
A公司将此优化版本标记为version2,然后通过日志服务性能监控平台的数据对比功能分析此次优化带来的性能差异。下图展示了version1与version2的差别,浅蓝色代表部分优化,深蓝色代表全部优化。通过对比发现,对正则表达式进行优化后,上层函数耗时下降77.29%。
验证优化
再次打开QPS统计日志,发现同等资源条件下,10次的平均QPS提升到1399左右,接口性能得到近40倍的提升。
内存优化
定位问题
A公司的应用程序是基于Go语言开发的,Go程序的内存性能指标分别为alloc_objects、alloc_space、inuse_objects以及inuse_space。其中:
alloc_*指标表示从程序启动到目前为止,分配对象的大小或数量,可以帮助定位频繁分配对象的方法,降低Go程序的GC压力。
inuse_* 指标表示内存中存在的对象大小或数量,可以帮助定位内存泄漏等问题。
A公司通过日志服务性能监控平台分析上述4类指标,发现应用程序的alloc_space指标存在异常,大量内存分配行为都存在于标准库的strings.split函数中。
分析问题
该应用程序的基本业务逻辑是从本地文件读取文件全量数据,然后按行进行切割,接着与输入的内容进行字符串匹配。按行切割时存在大量小字符串分配,通过slice偏移量截取字符串对比,可以避免大量小字符串的分配与释放,优化如下所示。
性能对比
A公司将此优化版本标记为version3,然后通过日志服务的数据对比功能分析此次改动带来的内存性能差异。下图展示了version3与version2的差别,浅蓝色代表部分优化,深蓝色代表全部优化。通过对比发现,过对本次字符串分配进行优化后,核心函数空间分配下降59.46%。
A公司推测内存分配空间的减少也意味着对象分配时间与GC时间的减少,因此推测本次优化可能还会带来CPU性能的提升,因此继续使用CPU性能对比查看CPU性能的变化,发现CPU性能确实提示了19.71%。
验证优化
再次打开QPS统计日志,发现同等资源条件下,10次的平均QPS提升到1840左右,性能再次获得了30%的提升。
总结
本实践中的两次优化,都是对标准库的使用优化。这些问题可能潜藏在每一位开发者的程序中。通过日志服务性能监控平台的查询、聚合、对比能力,您可以快速地定位应用程序潜藏的问题。