其实,这个问题也曾困扰过我。记得在参与第一个性能优化项目时,我每天的工作就是寻找代码中的低效率实现,然后进行修改重构,并验证性能提升效果,如此日复一日。所以,当时我很想说服团队 Leader 结束这个性能调优任务,但我首先连自己都说服不了。也正是基于这个原因,我才开始认真思考这个问题。
为什么会提出这个问题?
我们先来做一个假设,假如现在团队开发的一个软件产品需要进行性能调优,其指定的性能调优目标为提升 20%。那么,我们来思考一下,这个目标好达成吗?实际上,在进行深入的性能分析之前,我们很难回答这个问题。原因在于,不同软件的设计与实现存在很大差异,而针对性能这个模块,我们可以优化提升的空间各不相同。
我举个真实的例子,在我曾经参与的一个协议栈报文子系统的性能优化项目中,仅仅因为在代码实现优化中减少了一次内存拷贝,就一次性将系统处理的性能提升了 20%。然而,在我参与的另一个配置管理子系统的优化项目中,由于没有找到比较大的性能优化点,所以花费了很长时间与精力,才将性能提升了 10% 左右。
所以我才说,不同软件系统的性能优化提升效果和优化投入成本之间的关系差异很大,具体可以参考下图:
图片
在这个图中,有两个比较明显的规律值得你观察。其一,不同软件系统(软件 A 和软件 B)在性能调优的过程中,能够达到的性能提升百分比上限是不同的。其二,在性能调优的前期,投入很少成本就能获取比较好的性能提升效果;但在性能调优的中后期,要获取同样多的性能收益,需要花费的精力和成本会越来越大。
其实,在进行性能调优时,首要追求的目标应该是最大的投资收益比,也就是获取的性能优化收益值和消耗工作量成本之间的比值要最高。所以在理想情况下,我们应该将性能调优目标设定到一个性能提升临界值(通常会接近性能提升的上限)。如果达到这个临界值,就意味着即使后续进行再多的性能调优工作,我们能获取的性能收益都会越来越有限。那么在这个时候,我们就可以适当调整性能调优的节奏。如前面的示意图所示,软件 A 和软件 B 的性能调优目标设置的临界值,可能会在三角形所标识的位置附近。
但问题在于,对于一个软件系统来说,性能调优提升目标的临界值设定为多少才是合理的呢?我们又该如何确定这个临界值呢?
一般情况下,研发团队在设定性能调优目标时,会采取两种方式。第一种是以客户关注的性能需求目标为导向。比如我之前参与的百万表单数据查询分析优化项目,其核心目标就是让客户在操作过程中不卡顿,所以只需把查询请求响应时间优化到 1 秒内即可。第二种是以降低产品的部署运维成本为导向。这种方式通常会先确定一个性能提升百分比,比如将系统服务的响应时间降低 20%(从 100ms 到 80ms),减少产品部署使用的集群机器规模 20% 等等。
不过这里要注意,不管采用哪种方式制定的性能调优目标,都可能无法与软件优化可以达到的临界值完全匹配。在这种场景下,很容易导致性能调优的目标没有达成,但是性能调优任务却无法继续开展的情况。
所以,我们在性能调优的过程中,一定要谨记一点:未经分析就敲定性能优化的目标是不可取的。既然如此,那么正确开展和实施性能调优的方法步骤是什么呢?下面我就带你来分析分析。
正确开展性能调优的方法步骤
实际上,在很多研发团队的心目中,性能调优工作可能就是选择一款代码 Profiling 工具,然后针对软件执行期间进行性能分析,逐个寻找热点函数,最后进行修改和优化。然而,我们要知道,这种方法存在很大的局限性,它能够识别出的性能优化点非常有限。
比如说,并发设计、通信设计、IO 设计等软件设计引入的性能问题,它无法识别出来;不仅如此,软件编码实现层引入的性能问题,比如数据结构和算法选择等,它也都无法识别出来。
所以在这里,我根据以往参与的性能优化项目经验,总结出了实施性能调优的方法步骤。接下来,我就给你具体分析一下。
第一步,进行系统性的性能优化分析诊断。在此过程中,自顶向下地分析并识别所有可能导致性能劣化的可优化点。从这里输出的内容应当包含软件设计优化点、软件实现优化点等较为完整的列表,例如调整并发任务拆分、调整数据结构、选择性能优化模式等等。
第二步,分析调整性能调优目标值。这一步是指根据识别出的性能优化点,分析修改后的性能提升收益。需要注意的是,针对每个优化点的分析过程各不相同,且并没有统一的方法可供参考。
为了帮助你更好地理解这个过程,我举两个以前参与的性能优化案例来具体说明。
案例 1:一个协议栈报文子系统的性能优化项目。在这个项目中,我们通过性能优化分析诊断后发现,业务在处理过程中对报文数据执行了一次 copy 操作,而协议在处理过程中只修改了报文数据头部很少一部分字节的信息。在这种场景下,业务中的 copy 操作开销可以优化掉。那么优化修改后的性能提升值有多少呢?这里我根据 copy 的数据量在单板上进行了测量计算,在优化修改之前计算出了性能的预期收益。
案例 2:一个后端微服务的性能优化项目。在这个项目中,经过性能优化分析诊断后发现,业务存在很多慢查询操作,对软件性能影响较大。进一步分析后发现,这些慢查询所获取的数据其实很少变化,所以考虑采用缓存策略来优化性能。在这种场景下,可以根据慢查询的请求处理时延和请求的频次,分析计算出引入 Cache 场景下的性能提升收益。
总之,对于性能优化点来说,性能提升收益分析是一个非常重要的环节,不应被忽视。
第三步,按照成本收益逐步实施性能调优。
接下来,我们可以对性能优化点按照优先级进行排序,然后逐步修改并验证优化效果。在对性能优化点进行排序时,我们需要考虑的主要因素有几个:性能收益的大小、修改的工作量大小,以及对软件质量产生的影响(比如导致软件变复杂、引入故障风险高等)。
另外,这里要记住,如果对编译期选项配置优化和编码实现优化进行优先级排序,在同等性能收益的情况下,一般来说编译期优化的修改工作量会比较小,引入故障的风险率也比较低,所以优先级应该更高一些。
第四步,增加完善性能基线测试。
当性能调优完成合入后,就可以同步修改完善性能基线测试。然而,事实上很少有研发团队能够按照上述步骤来实施性能调优,因此在性能调优过程中容易陷入僵局,花费很大精力却并未给软件产品带来价值提升。在这个时候,研发团队就应该及时喊停,重新调整性能调优的工作方式与节奏。
什么时候需要喊停性能调优工作?
第一种性能调优反模式是:性能调优严重破坏了软件的质量。
这里举一个真实的案例。在我曾经参与的一个嵌入式系统性能优化项目中,原来的性能优化团队发现,通过宏替换个别函数调用会带来性能提升,于是几乎将代码中的所有函数都通过宏重新实现来整改替换。最后导致的后果是:大量的宏实现函数导致代码编写和阅读成本显著增大;同时在代码整改的过程中,引入了非常多的故障,而且很长时间无法得到很好的解决;更糟糕的是,最后的软件性能优化效果也没有达到预期。
其实,这种严重破坏软件设计质量的性能调优还是比较普遍的。比如,在代码中随意添加条件分支进行特殊处理,最后因为加入太多特殊流程,导致代码很难再添加新的业务特性。
第二种性能调优反模式是:盲目修改代码来尝试优化。
有的性能优化团队为了提升指令 Cache 命中率,会随机调整函数的位置。比如,把一个函数从一个文件中搬移到另外一个文件中;或者把一个函数从一个类搬移到另外一个类中,来判断 Cache 命中率是否有提升。这种性能调优方式,由于背后并没有理论指导,即使可以获取到一些短暂的性能提升收益,也是不稳定的,所以我们应该尽量避免这样做。
第三种性能调优反模式是:在业务的非性能瓶颈点上反复调优。
举个简单的例子,软件的查询请求处理的吞吐量,受制于底层网络传输带宽值的上限,理论上不可能再提升。这个时候,还在持续分析调优软件实现,期望提升吞吐量是没有任何意义的。
第四种性能调优反模式是:没有价值驱动的性能调优。
其实这种情况也挺常见,在软件系统中存在一些服务 / 组件(比如:操作事务记录,配置管理后台等),它们的处理性能并不会直接影响用户感受,而且占用的机器资源都很少,这时候如果还投入很大的工作量去优化软件性能,其实是没有意义的。