2.4 原则4:尽早频繁测试
这是第 4 条也是最后的原则。性能极客们(包括我)喜欢将性能测试作为开发周期不可或缺的一部分。理想情况下,在代码提交到中心源代码仓库前,性能测试就应该作为过程的一部分运行,如果代码引入了性能衰减,提交就会被阻止。
本章中,建议之间有些内在的冲突,而建议和现实之间也有冲突。好的性能测试包含了许多代码——至少中等规模的介基准测试是这样。它需要在新老代码上重复运行多次,以便确认性能真的有差别而不是随机变动。在大型项目中,这可能需要花费好几天或者一周时间,这使得在提交代码到仓库之间运行性能测试变得不那么现实。
通常的软件开发周期也没使事情变得更容易。项目日程通常会固定特性的发布日期:所有的代码变动必须在发布周期的早些时候就提交到源代码仓库,而剩下的时间则贡献给了将新版本中的缺陷(包括性能问题)抖落干净。这导致了提早测试的两个问题。
(1) 为了赶上项目进度,开发人员会在时间压力之下提交代码,而一旦有时间修复性能问题时,又变得踯躇不前。早期提交代码所导致的 1% 的性能衰减,开发人员愿意承受压力,修复问题。而等到功能特性截止夜才提交的代码,如果性能衰减 20%,开发人员就只能以后再处理了。
(2) 代码发生变化,性能也会随之而变。这个道理与测试全应用(以及可能有的模块测试)相同:堆内存的使用情况会改变,代码编译也会改变,等等。
开发过程中无论有多少困难,频繁的性能测试仍然很重要,即便有时候不能立刻解决问题。比如代码性能衰减了 5%,随着开发的推进,开发人员或许可以采用以下措施:如果他的代码依赖有待集成的功能特性,那就等该功能可用时,再稍微调整代码,性能衰减的问题或许就解决了。这是合理的情况,即便这意味着性能测试不得不几个星期都伴随着 5% 的性能衰减(这是不幸的事,却又无法避免,还可能掩盖了其他问题)。
另一方面,如果性能衰减只有等架构更改才能修复的话,那就最好在其余代码开始依赖新代码实现之前,尽早捕获和解决它。这是一种平衡,需要仔细分析,甚至还常常需要点政治技巧。
遵循以下准则,可以使得尽早频繁测试变得最有用。
自动化一切
所有的性能测试都应该脚本化(或者程序化,虽然脚本更简单)。全部环境都必须通过脚本安装和配置新代码(创建数据库连接、建立用户账号等),然后用脚本运行测试集。所谓自动化,还不止这些:脚本必须能够多次运行测试,对结果进行 t 检验分析,并能生成置信度报告,说明统计结果是相同,还是不同,如果不同,相差多少。
在测试运行前,必须通过自动化技术确保机器处于已知状态:必须检查是否有不希望运行的进程,操作系统配置是否正确,等等。只有每轮运行时保持相同的环境,性能测试才是可重复的。自动化过程中必须考虑这点。
测试一切
必须自动收集能想象到的每一点数据,以便进行后续分析。这些数据包括整个运行过程中采集的系统信息:CPU 使用率、磁盘使用率、网络使用率和内存使用率等。数据还包括应用的日志——应用产生的日志,以及垃圾收集器的日志。理想情况下,还应该包括 JFR 记录的信息(参见第 3 章),或者对系统影响较小的性能分析(profiling)信息,周期性线程堆栈,以及其他堆分析数据,例如直方图或者全堆的转储信息(尤其是全堆转储,需要占用大量空间,没有必要长期保留)。
如果适用的话,监控信息还必须包括系统其他部分的数据:例如,如果程序使用数据库,就应该包括数据库机器的系统统计数据,以及所有的数据库诊断输出(包括 Oracle 的 Automatic Workload Repository [AWR] 这样的性能报告)。
这些数据可以指导所有未被覆盖的回归分析。如果 CPU 使用率上升,就需要参考性能分析信息,弄清楚是什么花费了这么多时间。如果 GC 时间变长,就该查阅堆性能分析信息,搞明白是什么消耗了这么多内存。如果 CPU 和 GC 时间都减少,某些地方的竞争可能降低了性能:栈数据可以指示特定的同步瓶颈(参见第 9 章),JFR 记录可用来发现应用的延迟,数据库日志也可以发现数据库竞争加剧的线索。
当发现性能衰减源时,需要进一步巡查,找到更多可用数据,更多可以追踪的线索。正如第 1 章所讨论的,发生性能衰减的未必是 JVM。测量一切,从而确保分析的正确性。
在真实系统上运行
在单核笔记本上运行测试,与在 256 线程 SPARC CPU 机器上有很大的不同。从线程效应上来说,原因很清楚:机器规模越大,同时能运行的线程就越多,从而能减少应用线程对 CPU 的竞争。与此同时,大规模系统也会遇到小型笔记本上会被忽略的同步性能瓶颈。
还有其他重大的性能差异,即便乍一眼看上去不那么明显。许多重要的性能调优标志,它们的默认值是基于 JVM 运行的底层硬件系统计算出来的。平台和平台之间所编译出来的代码也不同。缓存——软件缓存以及更重要的硬件缓存——在不同系统和不同负载下也是不一样的,等等。
因此,除非在预期的负载和预期的硬件下测试,否则永远无法在测试中完全了解特定生产环境下的性能。可以在配置较低的硬件上运行规模较小的测试,以此来模拟和外推。在现实测试中,复制生产环境相当困难或昂贵。但外推只是简单的预测,即便在最好的情况下,预测也可能是错的。大规模系统远不只是将各部分加起来那么简单,没有什么测试能够代替在真实系统上的负载了。
快速小结
1. 虽然频繁的性能测试很重要,但并非毫无代价,在日常的开发周期中需要仔细斟酌。
2. 自动化测试系统可以收集所有机器和程序的全部统计数据,这可以为查找性能衰减问题提供必不可少的线索。
快速小结