Javascript学习笔记-程序性能和性能测试

2017-10-28  本文已影响0人  Patrick浩
Javascript性能和性能测试.png

这次的内容太多没有研究过了,只用过很少的一部分,所以仅仅作为初步的了解来进行总结。

1. 性能提升

1.1 WebWorker

WebWorker是HTML5提供的方案,是基于多线程的Javascript。如我们所知,Javascript是单线程运行的,自身并不具备多线程能力,但是Javascript的宿主环境,却可以包含多个Javascript引擎,多个Javascript引擎的并行执行,就可以实现多线程的能力。一般情况下会有一个主UI线程以及一个或多个Worker线程。
但是多线程运行可能导致数据共享会存在问题,所以WebWorker中主UI线程的数据是不能和Worker线程直接进行共享,他们之间的数据传递的方式进行数据沟通。

1.1.1 Worker

用一个例子来说明如何创建和使用一个Worker

main.js 
// 创建一个Worker
var w1 = new Worker('w1.js');
// 添加message事件监听
w1.addEventListener('message', function(evt){
  // 获取到子线程处理后返回的结果
})
// 发送数据到Worker中
w1.postMessage('patrick');

w1.js
// 添加message事件监听
addEventListener('message', function(evt){
  // 获取主线程中post的数据,这里可以理解为拿到'patrick'
  // 调用方法处理数据后使用postMessage返回给主线程
  postMessage('Hello, ' + evt.data);
})

Worker.js中可以通过importScripts()引入第三方的js文件,只是导入过程是同步阻塞的。
使用worker.terminate()方法可以终止worker线程

1.1.2 SharedWorker

使用Worker创建的子线程,在不同的浏览器tab中并不是公用的。每一个tab都会独立创建一个子线程,子线程中的变量在不同tab间并不通用,可以使用SharedWorker来使子线程在多个tab间的数据共享

 main.js 
// 创建一个Worker
var w2 = new SharedWorker('w2.js');
// 添加message事件监听,但是是绑定在port属性上
w2.port.addEventListener('message', function(evt){
  // 获取到子线程处理后返回的结果
})
// 发送数据到Worker中,也需要绑定在port属性上
w2.port.postMessage('patrick');
// 开启port
w2.port.start();

w2.js
var i = 0;
// 增加connect事件监听
addEventListener('connect', function(evt) {
  // 获取到port信息
  var port = evt.ports[0]
  // 添加message事件监听
  port.addEventListener('message', function(evt){
    // 变量i在每一个tab中共享
    port.postMessage(`Hello, ${evt.data} ${i} times`);
  })
  // 启动port
  port.start()
})

相比于普通的WorkerSharedWorker需要绑定到某个port,并对port进行事件监听,同时调用port.start()启动port
Web Worker要在Server上运行,否则Chrome会报错给予警告,虽然网上有说Safari可以不用运行在Server环境中,实际尝试过也无法运行

1.2 SIMD

SIMD(Single Instruction/Multiple Data) 单结构多数据,是一种放弃掉多线程,直接利用CPU提供的API进行位运算的一种提案,目前根据MDN网上的说法,这个提案已经被废弃,被WebAssembly取代,有兴趣的可移步MDN SIMD

1.3 asm.js

一种Javascript的编程规范,主要是通过代码规范,创建数据操作缓存堆,减少Javascript引擎进行的多余的操作,例如强制类型转换,垃圾回收等,从而提高Javascript性能,可以通过插件来将现有js代码进行转换,具体信息请移步MDN asm.js

1.4 WebAssembly

Javascript中引用其他类型语言并执行的解决方案,使用相关的API,可以在Javascript中引入C/C++等其他类型语言执行,并不影响现有Javascript代码,是目前尝试性质的解决方案MDN WebAssembly
关于WebAssembly会在自己学习研究之后专门写一篇总结,欢迎持续关注。

以上的1.1-1.4方案实际上均是在利用Javascript处理一些耗时或者较大数据量大问题:大数据分析,图形解析时提供的可参考方案

1.5 尾调用

尾调用是指函数运行的最后是一个函数调用,在ES6规范中需要各Javascript引擎强制去实现相关的优化。尾调用是属于函数式编式编程的范畴。

// 尾调用
function f1() {
  return f1();
}
// 非尾调用
function f2() {
  return f2() + 1;
}

Javascript引擎在运行过程中,会为每一个执行的函数创建一个栈帧,而对于尾调用来说,由于上一个函数已经结束,没有其他任何操作,所以当前函数的栈帧会自动分配给尾调用的函数,不会去创建新的栈帧,减小了内存的消耗,尤其是在我们使用递归的时候,避免递归带来的内存溢出,使用尾调用的递归也叫做尾递归。
更详细的了解可以参考阮一峰大神的尾调调用优化,里面有更详细的图文描述和扩展。

2. 性能测试

2.1 杂谈

我们当然是希望代码性能越优秀越好,同样的操作耗时越少,肯定方案是更好的,从表面上看这一点毋庸置疑,但是很多时候对最优代码追求的时候需要考虑很多因素,并不是所有的时候都要去追求最佳性能

  1. Javascript宿主环境不同,导致Javascript引擎对同一段代码的优化规则存在差异,所以不同环境下,同一段代码并不能始终保持最优
  2. 对于微观代码0.1ms和0.01ms的之间的导致的性能差距可能并不明显,所以拘泥于微观性能并没有太多用处
  3. 对于性能的优化和评定最好基于一个比较大的上下文中进行,这样的优化结果才更有意义

2.2 benchmark.js

通常我们如果要比较两段代码之间的性能,会在每段代码运行前添加获取一个时间startTime,在代码运行结束后再获取一个时间endTime,然后两者进行差值,再比较两段代码之间的差值,从而得出哪一段性能更佳

var startTime = Date.now();
// Todo 一些逻辑
var endTime = Date.now();
console.log(startTime - endTime);

这样的做法表面上来说是没有问题的,但是考虑的因素太少,一方面由于获取时间的过程存在消耗和精度问题,可能会影响测试结果;另一方面单次执行结果并不能很好的说明一段代码比另一段性能更好,可能多次运行之后结果会存在不同,虽然可以考虑添加循环来执行多次,并使用平均值来获取平均时间判断,但是在统计学上这样结果的可信度也不够高。
很庆幸有大牛深谙统计学知识,并帮助我们创建了一套可信的性能统计代码来帮助我们进行性能比较,也就是benchmark.js,官方有很详细的介绍。

2.3 jsPerf.com

基于benchmark.js提供了多宿主环境,多条件下的执行比较,相关资料也可以通过访问官方网站jsPerf.com

3. 参考

《你不知道的Javascript(中卷)》
使用benchmark.js和jsPerf.com进行性能分析
阮一峰——尾调用优化

上一篇下一篇

猜你喜欢

热点阅读