本文章仅针对我自己在看书过程中对一些不太清楚的知识点进行查漏补缺——《你不知道的JavaScript(中卷)》第二部分异步和性能中的第五章”程序与性能“、第六章”性能测试与调优“
Web Worker
为什么需要Worker
如果开发者在浏览器中有一些密集型的任务处理,浏览器是基于JavaScript语言的,而JS是单线程,是不是意味着我们在处理密集型任务时会阻塞浏览器,给用户带来不好的体验呢?
不是的。虽然JS是单线程的,但浏览器这样的环境可以提供多个JS引擎实例,各自运行在自己的线程上。程序中每一个独立的多线程部分被称为一个Web Worker
有什么特点
Worker 之间以及它们和主程序之间,不会共享任何作用域或资源,不需要考虑多线程编程的问题,只通过基本的事件消息机制相互联系
从 JavaScript 主程序(或另一个 Worker)中,可以这样实例化一个Worker,收发消息
var w1 = new Worker( "http://some.url.1/mycoolworker.js" );
w1.addEventListener( "message", function(evt){
// evt.data
} );
w1.postMessage( "something cool to say" );
URL 应该指向一个 JavaScript 文件的位置,这个文件将被加载到一个 Worker 中,然后浏览器启动一个独立的线程,让这个文件在这个线程中作为独立的程序运行
可以执行网络操作(Ajax、WebSockets)以及设定定时器。
可以访问几个重要的全局变量和功能的本地复本,包括navigator 、location 、JSON 和 applicationCache
可以通过 importScripts(..) 向 Worker 加载额外的 JavaScript脚本,但脚本加载是同步的,会阻塞接下来Worker的执行,直到文件加载和执行完成
共享Worker
如果你的站点或 app 允许加载同一个页面的多个 tab,那你可能非常希望通过防止重复专用 Worker 来降低系统的资源使用。在这一方面最常见的有限资源就是 socket 网络连接,因为浏览器限制了到同一个主机的同时连接数目。当然,限制来自于同一客户端的连接数也减轻了你的资源压力在这种情况下,创建一个整个站点或 app 的所有页面实例都可以共享的中心 Worker 就非常有用了
这称为 SharedWorker ,可通过下面的方式创建
var w1 = new SharedWorker( "http://some.url.1/mycoolworker.js" );
// 在共享Worker内部
w1.addEventListener("connect", function (evt) {
// 这个连接分配的端口
var port = evt.ports[0];
port.addEventListener("message", function (evt) {
// ..
port.postMessage();
// ..
});
// 初始化端口连接
port.start();
});
SIMD
单指令多数据(SIMD)是一种数据并行 方式,与Web Worker 的任务并行 (task parallelism)相对,因为这里的重点实际上不再是把程序逻辑分成并行的块,而是并行处理数据的多个位。
通过 SIMD,线程不再提供并行。取而代之的是,现代 CPU 通过数字“向量”(特定类型的数组),以及可以在所有这些数字上并行操作的指令,来提供 SIMD 功能。
性能测试
不准确的性能测试方案
var start = (new Date()).getTime(); // 或者Date.now()
// 进行一些操作
var end = (new Date()).getTime();
console.log( "Duration:", (end - start) );
测试的环境或者说平台精度不高
如果测试的结果是0ms,用户可能会认为执行时间小于1ms,但其实有的平台精度没有达到1ms,而是以更大的递归间隔更新定时器,比如IE早期版本精度只有15ms
测试环境可能会过度优化
JavaScript 引擎找到了什么方法来优化你这个独立的测试用例
for循环后取平均的错误思想
迭代 100 次,即使只有几个(过高或过低的)的异常值也可以影响整个平均值
要确保把异常因素排除,你需要大量的样本来平均化。你还会想要知道最差样本有多慢,最好的样本有多快,以及最好和最差情况之间的偏离度有多大
Benchmark.js
Benchmark.js是一个统计学上有效的性能测试工具
jsperf.com
如果想要得到可靠的测试结论,需要在很多不同的环境(桌面浏览器、移动设备,等等)中测试汇集测试结果
比如,针对同样的测试高端桌面机器的性能很可能和智能手机上Chrome 移动设备完全不同。而电量充足的智能手机上的结果可能也和同一个智能手机但电量只有 2% 时完全不同,因为这时候设备将会开始关闭无线模块和处理器
jsperf使用 Benchmark.js 库来运行统计上精确可靠的测试,并把测试结果放在一个公开可得的 URL上
尾调用
尾调用(TCO)就是一个出现在另一个函数“结尾”处的函数调用
ES6要求引擎实现TCO
function foo(x) {
return x;
}
function bar(y) {
return foo( y + 1 ); // 尾调用
}
function baz() {
return 1 + bar( 40 ); // 非尾调用
}
baz(); // 42
调用一个新的函数需要额外的一块预留内存来管理调用栈,称为栈帧
如果支持 TCO 的引擎能够意识到 foo(y+1) 调用位于尾部 ,这意味着 bar(..) 基本上已经完成了,那么在调用 foo(..) 时,它就不需要创建一个新的栈帧,而是可以重用已有的 bar(..) 的栈帧。这样不仅速度更快,也更节省内存