上下文为王
不要忘了检查一个指定的性能基准分析的上下文环境,特别是在X与Y之间进行比较时。仅仅因为你的测试显示X比Y速度快,并不意味着“X比Y快”这个结论是实际上有意义的。
举个例子,让我们假定一个性能测试显示出X每秒可以运行1千万次操作,而Y每秒运行8百万次。你可以声称Y比X慢20%,而且在数学上你是对的,但是你的断言并不向像你认为的那么有用。
让我们更加苛刻地考虑这个测试结果:每秒1千万次操作就是每毫秒1万次操作,就是每微秒10次操作。换句话说,一次操作要花0.1毫秒,或者100纳秒。很难体会100纳秒到底有多小,可以这样比较一下,通常认为人类的眼睛一般不能分辨小于100毫秒的变化,而这要比X操作的100纳秒的速度慢100万倍。
即便最近的科学研究显示,大脑可能的最快处理速度是13毫秒(比先前的论断快大约8倍),这意味着X的运行速度依然要比人类大脑可以感知事情的发生要快12万5千倍。X运行的非常,非常快。
但更重要的是,让我们来谈谈X与Y之间的不同,每秒2百万次的差。如果X花100纳秒,而Y花80纳秒,差就是20纳秒,也就是人类大脑可以感知的间隔的65万分之一。
我要说什么?这种性能上的差别根本就一点儿都不重要!
但是等一下,如果这种操作将要一个接一个地发生许多次呢?那么差异就会累加起来,对吧?
好的,那么我们就要问,操作X有多大可能性将要一次又一次,一个接一个地运行,而且为了人类大脑能够感知的一线希望而不得不发生65万次。而且,它不得不在一个紧凑的循环中发生5百万到1千万次,才能接近于有意义。
虽然你们之中的计算机科学家会反对说这是可能的,但是你们之中的现实主义者们应当对这究竟有多大可能性进行可行性检查。即使在极其稀少的偶然中这有实际意义,但是在绝大多数情况下它没有。
你们大量的针对微小操作的基准分析结果——比如++x
对x++
的神话——完全是伪命题,只不过是用来支持在性能的基准上X应当取代Y的结论。
引擎优化
你根本无法可靠地这样推断:如果在你的独立测试中X要比Y快10微秒,这意味着X总是比Y快所以应当总是被使用。这不是性能的工作方式。它要复杂太多了。
举个例子,让我们想象(纯粹地假想)你在测试某些行为的微观性能,比如比较:
var twelve = "12";
var foo = "foo";
// 测试 1
var X1 = parseInt( twelve );
var X2 = parseInt( foo );
// 测试 2
var Y1 = Number( twelve );
var Y2 = Number( foo );
如果你明白与Number(..)
比起来parseInt(..)
做了什么,你可能会在直觉上认为parseInt(..)
潜在地有“更多工作”要做,特别是在foo
的测试用例下。或者你可能在直觉上认为在foo
的测试用例下它们应当有同样多的工作要做,因为它们俩应当能够在第一个字符"f"
处停下。
哪一种直觉正确?老实说我不知道。但是我会制造一个与你的直觉无关的测试用例。当你测试它的时候结果会是什么?我又一次在这里制造一个纯粹的假想,我们没实际上尝试过,我也不关心。
让我们假装X
与Y
的测试结果在统计上是相同的。那么你关于"f"
字符上发生的事情的直觉得到确认了吗?没有。
在我们的假想中可能发生这样的事情:引擎可能会识别出变量twelve
和foo
在每个测试中仅被使用了一次,因此它可能会决定要内联这些值。然后它可能发现Number("12")
可以替换为12
。而且也许在parseInt(..)
上得到相同的结论,也许不会。
或者一个引擎的死代码移除启发式算法会搅和进来,而且它发现变量X
和Y
都没有被使用,所以声明它们是没有意义的,所以最终在任一个测试中都不做任何事情。
而且所有这些都只是关于一个单独测试运行的假设而言的。比我们在这里用直觉想象的,现代的引擎复杂得更加难以置信。它们会使用所有的招数,比如追踪并记录一段代码在一段很短的时间内的行为,或者使用一组特别限定的输入。
如果引擎由于固定的输入而用特定的方法进行了优化,但是在你的真实的程序中你给出了更多种类的输入,以至于优化机制决定使用不同的方式呢(或者根本不优化!)?或者如果因为引擎看到代码被基准分析工具运行了成千上万次而进行了优化,但在你的真实程序中它将仅会运行大约100次,而在这些条件下引擎认定优化不值得呢?
所有这些我们刚刚假想的优化措施可能会发生在我们的被限定的测试中,但在更复杂的程序中引擎可能不会那么做(由于种种原因)。或者正相反——引擎可能不会优化这样不起眼的代码,但是可能会更倾向于在系统已经被一个更精巧的程序消耗后更加积极地优化。
我想要说的是,你不能确切地知道这背后究竟发生了什么。你能搜罗的所有猜测和假想几乎不会提炼成任何坚实的依据。
难道这意味着你不能真正地做有用的测试了吗?绝对不是!
这可以归结为测试 不真实 的代码会给你 不真实 的结果。在尽可能的情况下,你应当测试真实的,有意义的代码段,并且在最接近你实际能够期望的真实条件下进行。只有这样你得到的结果才有机会模拟现实。
像++x
和x++
这样的微观基准分析简直和伪命题一模一样,我们也许应该直接认为它就是。