优秀的编程知识分享平台

网站首页 > 技术文章 正文

JS性能测试的误解迷思你遇到过吗?Benchmark帮你解决

nanyue 2024-08-05 20:02:57 技术文章 8 ℃

引言

前端开发中,掌握好浏览器的特性进行有针对性的性能调优是一项基本工作,同时,比较不同代码的执行速度也是一项关键的工作。最能引发软件攻城狮普遍好奇心的领域之一(真的,有些开发者可能会沉迷于此)就是分析和测试编写一行或一块代码的多个选择,然后确定哪一种更快。

如何最精确可靠地测试 JavaScript 性能,本文将从我们经常采用的方法,筛选掉所有垃圾以得到清晰的概念。

性能测试的误解和迷思

  • 代码段运行时长

如果被问到如何测试某个运算的速度(执行时间),绝大多数 JavaScript 开发者都会从类似下面的代码开始:

var start = (new Date()).getTime(); // 或者Date.now()

// 进行一些操作

var end = (new Date()).getTime();

console.log( "Duration:", (end - start) );

这个测量方式到底能告诉你什么呢?理解它做了什么以及关于这个运算的执行时间不能提供哪些信息,就是学习如何正确测试 JavaScript 性能的关键所在。

如果报告的时间是 0,可能你会认为它的执行时间小于 1ms。但是,这并不十分精确。有些平台的精度并没有达到 1ms,而是以更大的递增间隔更新定时器。比如, Windows(也就是 IE)的早期版本上的精度只有 15ms,这就意味着这个运算的运行时间至少需要这么长才不会被报告为 0 !

还有,不管报告的时长是多少,你能知道的唯一一点就是,这个运算的这次特定的运行消耗了大概这么长时间。而它是不是总是以这样的速度运行,你基本上一无所知。你不知道引擎或系统在这个时候有没有受到什么影响,以及其他时候这个运算会不会运行得更快。

如果时长报告是 4 呢?你能更加确定它的运行需要大概 4ms 吗?不能。它消耗的时间可能要短一些,而且在获得 start 或 end 时间戳之间也可能有其他一些延误。

更麻烦的是,你也不知道这个运算测试的环境是否过度优化了。有可能 JavaScript 引擎找到了什么方法来优化你这个独立的测试用例,但在更真实的程序中是无法进行这样的优化的,那么这个运算就会比测试时跑得慢。

那么,能知道的是什么呢?很遗憾,根据前面提出的内容,我们几乎一无所知。这样低置信度的测试几乎无力支持你的任何决策。这个性能测试基本上是无用的。更坏的是,它是危险的,因为它可能提供了错误的置信度,不仅是对你,还有那些没有深入思考带来测试结果的条件的人员。

  • 重复执行相同代码

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

并不完全是这样。

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

你也可以不以固定次数执行运算,转而循环运行测试,直到达到某个固定的时间。这可能会更可靠一些,但如何确定要执行多长时间呢?你可能会猜测,执行时间应该是你的运算执行的单次时长的若干倍。错。

实际上,重复执行的时间长度应该根据使用的定时器的精度而定,专门用来最小化不精确性。定时器的精度越低,你需要运行的时间就越长,这样才能确保错误率最小化。 15ms 的定时器对于精确的性能测试来说是非常差劲的。要最小化它的不确定性(也就是出错率)到小于 1%,需要把你的每轮测试迭代运行 750ms。而 1ms 定时器时只需要每轮运行 50ms就可以达到同样的置信度。

但是,这只是单独的一个例子。要确保把异常因素排除,你需要大量的样本来平均化。你还会想要知道最差样本有多慢,最好的样本有多快,以及最好和最差情况之间的偏离度有多大,等等。你需要知道的不仅仅是一个告诉你某个东西跑得有多快的数字,还需要得到某个可以计量的测量值告诉你这个数字的可信度有多高。

还有,你可能会想要把不同的技术(以及其他方面)组合起来,以得到所有可能方法的最佳平衡。

这仅仅是个开始。如果你过去进行性能测试的方法比我刚才提出的还要不正式的话,好吧,那么可以说你完全不知道:正确的性能测试。

正确的方法:使用Benchmark.js

任何有意义且可靠的性能测试都应该基于统计学上合理的实践。幸运的是,像 John-David Dalton 和 athias Bynens 编写了一个统计学上有效的性能测试工具,名为 Benchmark.js(http://benchmarkjs.com/)。因此,对于这个问题,使用这个工具就好了。

下面举二个例子进行说明。

例一 比较RegExp的test方法和String对象的indexOf方法查找字符串谁的速度更快

当我们想比较RegExp的test方法和String对象的indexOf方法查找字符串谁的速度更快的话,js代码在不同的浏览器,不同的操作系统环境运行的效率可能是不一样的,这就是为什么我们需要对其进行基准测试,在做基准测试方面,我们可以使用Benchmark.js和使用jsPerf(一个基于JSLitmus的基准测试库)。

首先我们在系统根目录下,通过npm intsall benchmark 来安装benchmark。该模块会被写入node_modules文件夹中,我们在test.js文件中通过require方法引入该模块。

将如下代码写入test.js文件,该文件置于系统根目录下:

var Benchmark = require('benchmark');

var suite = new Benchmark.Suite;

// 添加测试

suite.add('RegExp#test', function() {

/o/.test('Hello World!');

})

.add('String#indexOf', function() {

'Hello World!'.indexOf('o') > -1;

})

// add listeners

.on('cycle', function(event) {

console.log(String(event.target));

})

.on('complete', function() {

console.log('Fastest is ' + this.filter('fastest').pluck('name'));

})

// run async

.run({ 'async': true });

然后在终端执行node test.js 可见输出结果如下:

? ~ git:(master) ? node test.js

RegExp#test x 9,847,928 ops/sec ±1.47% (83 runs sampled)

String#indexOf x 23,366,017 ops/sec ±0.91% (96 runs sampled)

Fastest is String#indexOf

结果最快的就是String对象的indexOf方法,其中,Ops/sec 测试结果以每秒钟执行测试代码的次数(Ops/sec)显示,这个数值越大越好。除了这个结果外,同时会显示测试过程中的统计误差,以及相对最好的慢了多少(%)

例二 call和apply的比较

var Benchmark = require('benchmark');

var suite = new Benchmark.Suite;

var arr1 = function (str) {

return [].slice.apply(str);

};

var str2 = function (str) {

return [].slice.call(str);

};

// 添加测试

suite.add('arr1', function() {

arr1('test');

})

.add('str2', function() {

str2('test');

})

// add listeners

.on('cycle', function(event) {

console.log(String(event.target));

})

.on('complete', function() {

console.log('Fastest is ' + this.filter('fastest').pluck('name'));

})

// run async

.run({ 'async': true });

输出如下内容:

arr1 x 596,505 ops/sec ±1.14% (95 runs sampled)

str2 x 627,822 ops/sec ±1.27% (92 runs sampled)

Fastest is str2

Tags:

最近发表
标签列表