2.3 原则3:用统计方法应对性能的变化

第 3 条原则讲的是性能测试的结果会随时间而变。即便程序每次处理的数据集都相同,产生的结果也仍然会有差别。因为有很多因素会影响程序的运行,如机器上的后台进程,网络时不时的拥堵等。好的基准测试不会每次都处理相同的数据集,而是会在测试中制造一些随机行为以模拟真实的世界。这就会带来一个问题:运行结果之间的差别,到底是因为性能有变化,还是因为测试的随机性。

可用以下方法来解决这个问题,即多次运行测试,然后对结果求平均。当被测代码发生变化时,就再多次运行测试,对结果求平均,然后比较两个平均值。这听起来似乎很容易。

不幸的是,事情并没有想象中那么简单。要想弄清楚测试间的差别是真实的性能变化还是随机变化并不容易——这就是性能调优的关键所在,不仅需要科学引领道路,还需要懂点艺术才能玩得转。

比较基准测试的结果时,我们不可能知道平均值的差异是真的性能有差还是随机涨落。最好的办法是先假设“平均值是一样的”,然后确定该命题为真时的几率。如果命题很大几率为假,我们就有信心认为平均值是真的有差别(虽然我们永远无法 100% 肯定)。

像这种因代码更改而进行的测试称为回归测试。在回归测试中,原先的代码称为基线(baseline),而新的代码称为试样(specimen)。考虑一个批处理程序的案例,基线和试样都执行 3 次,表 2-2 给出了所用的时间。

表2-2:假设两组测试的执行时间

迭代 基线(秒) 试样(秒)
第 1 次 1.0 0.5
第 2 次 0.8 1.25
第 3 次 1.2 0.5
平均值(秒) 1 0.75

试样的平均值表明代码性能改善了 25%。这个测试所反映出来的 25% 的改善,我们真的能相信多少?看上去很美好:试样中 3 个有 2 个的值小于基线平均值,看起来改进的步子很大——但是如果用本节介绍的方法分析这些结果就会得出结论,试样和基线性能相同的概率有 43%。观察到的这些数字说明,两组测试的基本性能在 43% 的时间内是相同的,因此性能不相同的时间只占 57%。顺便说一句,57% 的时间内性能不相同和性能改善 25% 也完全不是一回事,稍后讨论这个问题。

上述概率看起来和我们的预期有差别,其原因是测试的结果变化很大。一般来说,结果数据差别越大,就越难判断平均值之间的差异是真实的差别还是随机变动。

此处的数字 43%,是学生 t 检验(Student's t-test,以下称 t 检验)得出的结果,这是一种针对一组数据及其变化的统计分析。顺便说一句,“学生”是首次发表该检验的科学家 5 的笔名,而不是提醒你(至少我)那些年在学校睡过的统计学课。t 检验计算出的 p 值,是指原假设(null hypothesis)成立时 6 的概率。(有一些程序和类库可以计算 t 检验,本节的结果是用 Apache Commons Mathematics 类库中的 TTest 计算的。)

5即威廉·戈斯特。——译者注

6原文为“false”,有误。——译者注

回归测试中的原假设是指假设两组测试的性能一样。这个例子中的 p 值大约为 43%,意思是我们相信这两组测试平均值相同的概率为 43%。相反,我们相信平均值不同的概率为 57%。

57% 意味着什么,两组测试的平均值不相同?严格来讲,这并不意味着我们相信性能改善 25% 的概率有 57%——它只是意味着,我们相信结果不同的概率为 57%。性能可能改善了 25%,也可能 125%,甚至试样的实际性能也许比基线还糟糕。最大的可能则是测量出来的差别就是接近于真实的差异(特别是随着 p 值下降越是如此),只是我们永远无法肯定这点。

统计学及其语义

正确表述 t 检验结果的语句应该像这样:试样与基线有差别的可能性为 57%,差别预计最大有 25%。

不过通常会这么描述:结果改善 25% 的置信度(confidence level)为 57%。确切地说,这种说法与前面并不一致,也会让统计学家们抓狂,不过这种说法简短而易于为人接受,也不算太离谱。统计学分析经常会涉及不确定性,如果语义可以精确地陈述,自然能让人更好地理解这种不确定性。不过对于那些基础问题已经很清楚的领域,语义描述上有些悄然简化也是在所难免的。

t 检验通常与 α 值一起使用,α 值是一个点(有点随意),如果结果达到这个点那就是统计显著性(statistical significance)。通常 α 值设置为 0.1——意思是说,如果试样和基线只在 10%(0.1)的时间里相同(或反过来讲,90% 的时间里试样和基线有差异),那结果就被认为是统计显著。其他常用的 α 值还有 0.05(置信度为 95%)或 0.01(置信度为 99%)。如果测试的 p 值小于 1-α 值,则被认为是统计显著。

因此,查找代码性能变化的正确方法是先决定一个显著性水平——比如 0.1——然后用 t 检验判定在这个显著性水平上试样是否与基线有差别。请仔细搞明白,如果显著性测试失败,意味着什么。在这个例子中,p 值为 0.43,在置信度为 90% 的情况下我们不能说有显著性差异,而结果表示平均值不相同。事实上,测试没有显著性差异并不意味着结果无关紧要,它仅仅表示这个测试没法形成定论。

统计学中的显著性与重要性

显著性差异并不意味着统计结果对我们更重要。平均为 1 秒的变化很小的基线,和平均为 1.01 秒的变化很小的试样,其 p 值可能为 0.01:结果的差别有 99% 的置信度。

但结果的差别只有 1%。现在假定另外一个测试,试样和基线有 10% 的变动,但是 p 值为 0.2,即非统计显著。哪个测试的结果最为重要?这需要更多时间来审查。

审查后发现,虽然相差 10% 的测试的置信度低,但在用时上更加优化(如果可能的话,可以用更多数据来验证测试结果是否真的统计显著)。仅仅因为 1% 差异的可能性更大,并不意味着它更重要。

从统计学上说,测试不能得出定论通常是因为样本数据不足。迄今为止,示例所考虑的基线和试样各是 3 次迭代。再加 3 次迭代,结果会变成这样:基线的迭代结果分别是 1、1.2 和 0.8 秒,试样的迭代结果分别是 0.5、1.25 和 0.5 秒?随着数据的增加,p 值就从 0.43 跌落到了 0.19,这意味着结果有差异的概率从 57% 上升到了 81%。运行更多测试迭代,再加 3 个数据点后,概率则增加到了 91%——超过了常规的统计显著性水平。

运行更多测试迭代从而达到某个统计显著性水平的方法并不总是可行。严格来说,这么做也没有必要。实际上,用以判定统计显著性的 α 值可以任意选择,虽然通常选的都是普遍认可的值。置信度为 90% 时,p 值 0.11 不是统计显著,但置信度为 89% 时,它就是统计显著了。

这里得到结论:回归测试并不是非黑即白的科学。对于一组数据,不经过统计分析你就没法弄清楚数字的含义,也就没法进行比较和判断。此外,由于概率的不确定性,即便用统计分析也不能给出完全可靠的结果。性能调优工程师的工作就是:考虑一堆数据(或者他们的均值)、弄清各种概率、决定往哪使力。

2.3 原则3:用统计方法应对性能的变化 - 图1 快速小结

1. 正确判定测试结果间的差异需要统计分析,通过统计分析才能确定这些差异是不是归因于随机因素。

2. 可以用严谨的 t 检验来比较测试结果,实现上述目的。

3. t 检验可以告知我们变动存在的概率,却无法告诉我们哪种变动该忽略,而哪种该追查。如何在两者之间找到平衡,是性能调优工程的艺术魅力所在。