阅读 444

性能测试与调优

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动

前言

还记得当年,被面试官问到如何测试某个运算的速度(执行时间),我是这样回到的。

var start = (new Date()).getTime(); // 或者Date.now() 
// 进行一些操作之后
var end = (new Date()).getTime(); 
console.log( "Duration:", (end - start) );
复制代码

但是这种方案是错误的哦

为什么呢?

这个运算的这次特定的运行消 耗了大概这么长时间。而它是不是总是以这样的速度运行,你基本上一无所知。你不知道 引擎或系统在这个时候有没有受到什么影响,以及其他时候这个运算会不会运行得更快。这样低置 信度的测试几乎无力支持你的任何决策。这个性能测试基本上是无用的。

重复

很多人可能会用一个循环把它包起来,这样整个测试的运行时间就会更长 一些了。如果重复一个运算 100 次,然后整个循环报告共消耗了 137ms,那你就可以把 它除以 100,得到每次运算的平均用时为 1.37ms,但是是不对得。

简单的数学平均值绝对不足以对你要外推到整个应用范围的性能作出判断。迭代 100 次, 即使只有几个(过高或过低的)的异常值也可以影响整个平均值,然后在重复应用这个结 论的时候,你还会扩散这个误差,产生更大的欺骗性。

Benchmark.js

我并不打算复述他们的整个文档来介绍 Benchmark.js 如何运作。他们的 API 很不错,你 应该读一读。还有一些很棒的文章介绍了更多的细节和方法,比如这里(http://calendar. perfplanet.com/2010/bulletproof-javascript-benchmarks)和这里(monsur.hosa.in/2012/12/11/ benchmarksjs.html)。

function foo() {
    // 要测试的运算
}
var bench = new Benchmark(
    "foo test", // 测试名称
    foo, // 要测试的函数(也即内容)
    {
        // .. // 可选的额外选项(参见文档)
       
    });
     bench.hz; // 每秒运算数 
     bench.stats.moe; // 出错边界
     bench.stats.variance; // 样本方差
     // ..
复制代码

环境为王

测试条件与你期望的真实情况越接近越好。只有这 样得出的结果才有可能接近事实。

jsPerf.com

jsPerf 们前面介绍的 Benchmark.js 库来运行统计上精确可靠的测试,并把测试结果放在一个公开 可得的 URL 上,你可以把这个 URL 转发给别人。

写好测试

要写好测试,需要认真分析和思考两个测试用例之间有什么区别,以及这些区别是有意还 是无意的。

不要试图窄化到真实代码的微小片段,以及脱离上下文而只测量这一小部分的性能,因为 包含更大(仍然有意义的)上下文时功能测试和性能测试才会更好。这些测试可能也会运 行得慢一点,这意味着环境中发现的任何差异都更有意义。

微性能

很多时候,我们通过代码得手段做的一些性能上的优化可能在引擎眼中差异并不是非常大,譬如++i喝i++这样的行为。

不是所有的引擎都类似

引擎可以自由决定一个运算是否需要优化,可能进行权衡,替换掉运算次要性能。对一个 运算来说,很难找到一种方法使其在所有浏览器中都运行得较快。

大局

怎么知道什么是大局呢?首先要了解你的代码是否运行在关键路径上。如果不在关键路径 上,你的优化就很可能得不到很大的收益。

尾调用优化

尾调用就是一个出现在另一个函数“结尾”处的函数调用。这个调用结束后就没有其余事情要做了。

非递归的尾调用:

function foo(x) {
    return x;
}

function bar(y) {
    return foo(y + 1); // 尾调用
}

function baz() {
    return 1 + bar(40); // 非尾调用 
}
baz(); // 42
复制代码

foo(y+1) 是 bar(..) 中的尾调用,因为在 foo(..) 完成后,bar(..) 也完成了,并且只需要 返回 foo(..) 调用的结果。然而,bar(40) 不是尾调用,因为在它完成后,它的结果需要加 上 1 才能由 baz() 返回。

小结

  • 对一段代码进行有效的性能测试,特别是与同样代码的另外一个选择对比来看看哪种方案
    更快,需要认真注意细节;
  • 尾调用优化是 ES6 要求的一种优化方法,它使 JavaScript 中原本不可能的一些递归模式变 得实际。
文章分类
前端
文章标签