6.2 环境为王
对特定的性能测试来说,不要忘了检查测试环境,特别是比较任务 X 和 Y 这样的比对测试。仅仅因为你的测试显示 X 比 Y 快,并不能说明结论 X 比 Y 快就有实际的意义。
举例来说,假定你的性能测试表明 X 运算每秒可以运行 10 000 000 次,而 Y 每秒运行 8 000 000 次。你可以说 Y 比 X 慢了 20%。数学上这是正确的,但这个断言并不像你想象的那么有意义。
让我们更认真地思考这个结果:每秒 10 000 000 次运算就是每毫秒 10 000 次运算,每微妙 10 次。换句话说,单次运算需要 0.1μs,也就是 100ns。很难理解 100ns 到底有多么短。作为对比,据说人类的眼睛通常无法分辨 100ms 以下的事件,这要比 X 运算速度的 100ns 慢一百万倍了。
即使最近的科学研究表明可能大脑可以处理的最快速度是 13ms(大约是以前结论的 8 倍),这意味着 X 的运算速度仍然是人类大脑捕获一个独立的事件发生速度的 125 000 倍。X 真的非常非常快。
不过更重要的是,我们来讨论一下 X 和 Y 的区别,即每秒 2 000 000 次运算差距的区别。如果 X 需要 100ns,而 Y 需要 80ns,那么差别就是 20ns,这在最好情况下也只是人类大脑所能感知到的最小间隙的 65 万分之一。
我要说的是什么呢?这些性能差别无所谓,完全无所谓!
但是稍等,如果这些运算将要连续运行很多次呢?那么这个差别就会累加起来,对不对?
好吧,那我们要问的就是,这个运算 X 要一个接一个地反复运行多次的可能性有多大呢,得运行 650 000 次才能有一点希望让人类感知到。更可能的情况是,它得在一个紧密循环里运行 5 000 000~10 000 000 次才有意义。
你脑子里的计算机科学家可能抗议说,这是可能的;但你脑子里那个现实的的你会更大声说还是应该检查一下这个可能性到底有多大。即使在很少见的情况下是有意义的,但在绝大数情况下它却是无关紧要的。
对于微小运算的绝大多数测试结果,比如 ++x 对比 x++ 的迷思,像出于性能考虑应该用 X 代替 Y 这样的结论都是不成立的。
引擎优化
你无法可靠地推断,如果在你的独立测试中 X 比 Y 要快上 10μs,就意味着 X 总是比 Y 要快,就应该总是使用 X。性能并不是这样发挥效力的。它要比这复杂得多。
举例来说,设想一下(纯粹假设)你对某些微观性能行为进行了测试,比如这样的比较:
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 并没有被使用,因此将其标识为无关紧要的,故而在整个测试中实际上什么事情都没有做。
所有这些都只是根据单个测试所做的假设的思路。现代引擎要比我们凭直觉进行的推导复杂得多。它们会实现各种技巧,比如跟踪记录代码在一小段时期内或针对特别有限的输入集的行为。
如果引擎由于固定输入进行了某种优化,而在真实程序中的输入更加多样化,对优化决策影响很大(甚至完全没有)呢?或者如果引擎看到测试由性能工具运行了数万次而进行优化,但是在真实程序中只会运行数百次,而这种情况下引擎认为完全不值得优化呢?
我们设想的所有这些优化可能性在受限的测试中都有可能发生,而且在更复杂的程序中(出于各种各样的原因),引擎可能不会进行这样的优化。也可能恰恰相反,引擎可能不会优化这样无关紧要的代码,但是在系统已经在运行更复杂的程序时可能会倾向于激进的优化。
这里我要说明的就是,你真的不能精确知道底下到底发生了什么。你能进行的所有猜想和假设对于这样的决策不会有任何实际的影响。
这是不是意味着无法真正进行任何有用的测试呢?绝对不是!
这可以归结为一点,测试不真实的代码只能得出不真实的结论。如果有实际可能的话,你应该测试实际的而非无关紧要的代码,测试条件与你期望的真实情况越接近越好。只有这样得出的结果才有可能接近事实。
像 ++x 对比 x++ 这样的微观性能测试结果为虚假的可能性相当高,可能我们最好就假定它们是假的。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论