Javascript性能优化

来源:互联网 发布:八爪鱼三脚架 知乎 编辑:程序博客网 时间:2024/05/21 13:59

JavaScript性能研究

@author huangteng


前言

有关javascript性能的探究,目的只是找出性能提升的最佳途径。
我们执着于性能提升,也就是为了给用户带来最好的体验。
其实一说起性能,大概脑子里第一反应就是同步执行和异步执行。
当然,大家都会选择交替的并发执行(异步)的方式,这样减少阻塞,使得
我们的应用更加流畅。

本次探究我们分为两个层次,一个是程序级别的性能研究,一个是微性能的研究,比如
a++ , ++a 哪个更快?

再次谈谈javascript实现多线程

我们都知道javascript是单线程的,而且目前仍然是这样,不会诞生别的变化。我们也知道,如果能够像多cpu机器一样并行执行多任务,当然是最佳方案。

既然javaScript办不到,我们想想别的呢,HTML5的新特性,Worker,从浏览器实现了多线程的方式运行我们的js代码。其实也就是浏览器可以同时运行多个Js引擎,从而实现多线程式的效果。但是这样的方式又不想我们熟悉的多线程语言,比如java,C++等,多线程之间共享作用域和资源,这样对资源的访问必须依靠锁机制来维持顺序。

Worker建立的多线程并不会共享作用域和资源,线程之间完全依靠事件消息机制相互联系

Web Worker在耗时计算密集型操作中,显得特别实用。在WebWorker中我们可以实现:
1. 可以加载一个JS进行大量的复杂计算而不挂起主进程,并通过postMessage,onmessage进行通信;
2. 可以在worker中通过importScripts(url)加载另外的脚本文件;这些脚本加载是同步的。也就是说,importScripts(..) 调用会阻塞余下 Worker 的执行,直到文件加载和执行完成。
3. 可以使用 setTimeout(),clearTimeout(),setInterval(),clearInterval();
4. 可以使用XMLHttpRequest、WebSockets来发送请求,以及访问navigator、location 、JSON 和 applicationCache 的部分属性。

Worker中的数据传递现在不仅仅只依靠字符串了,感兴趣了解一下结构化克隆数据和
transferrable

有关Worker的用法,如下:

主线程:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>main process</title></head><body>    <div>        <h1>这是主线程中的页面</h1>    </div>    <div style=" width: 500px ;height:500px ;background: #e8e8e8">        <div  style="width: 500px ;height:200px ;background: #e8e8e8">            <h3 id="result"></h3>        </div>        <div style="width: 500px ; height: 100px ;position: absolute; bottom: 0px">            <button style="width: 80px ;height:30px;border-radius: 5px" onClick="start()">startWorker</button>            <button style="width: 80px ;height:30px;border-radius: 5px" onClick="end()">endWorker</button>        </div>    </div></body><script type="text/javascript">    // 利用python启动一个简单的服务    // 建立一个worker,参数是一个js文件路径    // 工作线程里面一般是很耗cpu的操作,这里的demo简单的实现主线程和工作线程之间的通话。    var w1;    function start(){        if(typeof(Worker) !== 'undefined'){            if(typeof(w1) === 'undefined'){                w1 = new Worker('/worker1.js');            }            // 建立与工作线程的通信;postMessgae-发送信息;onmessage-监听接收信息;在工作线程里面是相对的操作。            var i = 100;            w1.postMessage(i);            w1.onmessage = function(evt){                document.getElementById('result').innerHTML=evt.data;            }        }else{            document.getElementById('result').innerHTML = 'current browser not support worker!';        }    }    // 停止工作线程    function end(){        w1.terminate()    }</script></html>

工作线程 work.js:

(function(){    console.log('worker: ','<h4>这是一个Worker里的</h4>')    var rep = 0 ;    addEventListener('message',function(evt){        rep = evt.data + 200;    })    setTimeout(function(){        postMessage('给你的哦!'+ rep);    },2000)})()

更多内容比如sharedWorker,就是几个页面共享一个工作线程。
假如下面的页面有多个

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>main2</title></head><body>    <input type="text" style="width: 200px;height:25px;border-radius: 5px;background: #e8e8e8" id="text" /><input type="button" onClick="start()" style="margin-left:30px;width: 50px;height:25px;border-radius: 5px;background: #e8e8e8" value="send"/>    <script type="text/javascript">        (function callee(){            var itv=setTimeout(function(){                console.log("timeout!");                callee();            },1000);            // 这里主要验证一下sharedWorker            // 方法就简单写了,主要说明sharedWorker的消息传递            // 共享worker就是几个页面或者tab共享一个worker,他们之间的消息传递就需要通过port来区别开来            // 这里的demo通过一个页面的输入在另一个页面显示,中转的地方是sharedworker来演示            new SharedWorker('sharedworker.js').port.onmessage = function(evt){                if(evt.data.name=="connected")                    clearTimeout(itv),console.log("connected~");            }            console.log("connecting...")        })()    </script></body></html>

共享线程,sharedworker.js:

onconnect=function(e){  e.ports[0].postMessage({name:"connected"});};

可以参考:https://www.ibm.com/developerworks/cn/web/1112_sunch_webworker/

性能测试与调优

上述部分是针对程序级别的宏观的性能研究,其实这完全依赖与Ecma标准的发展,未来不排除激进的让javaScript变成多线程的语言。

本节的内容主要针对的是微性能的探究。

代码测试

js中对一段代码测试,大多数人采用的方法是这样的

var start = Date.now();// 这里是你的测试片段...var end = Date.now();console.log('run time',end-start);

这样的测试基本是无效的,你要考虑平台精度,方法执行的延误,样本的数量,最大最小的离散偏差…

测试应该是统计学的结果。我们不擅长无所谓,可以借助第三方工具,比如Benchmark.js
、jsPerf等…

这里以Benchmark.js的使用为案例,写一个测试demo,有兴趣的可以自己学习一下上面说的两种测试工具的使用。

/* * @author:  * @description: 测试 "+" 和 join性能 * @version: * @time: */ const Benchmark = require('benchmark') const suite = new Benchmark.Suite; var test1 = ()=>{    var res = '';    var num = 100000;    while(num--){        res += '<a></a>'    } } var test2 = ()=>{    var arr = [];    var num = 100000;    while(num--){        arr.push('<a></a>')    }    arr = arr.join(''); } suite.add('+ test',function(){    test1(); }).add('join test',function(){    test2(); }).on('cycle',function(evt){    console.log(String(evt.target)) }).on('complete',function(){    console.log('Fastest is '+ this.filter('fastest').map('name')) }).run({'async':true})

运行结果:

+ test x 583 ops/sec ±8.44% (51 runs sampled)join test x 184 ops/sec ±8.36% (55 runs sampled)Fastest is + test[Finished in 25.5s]

说明在100000次简单拼接的时候,字符串”+”拼接更快
其中ops/sec代表每秒执行次数,这个值越大说明性能越好
后面的XXX%代表系统误差。

benchmark的缺陷就是不能代表所有的环境,假如你在IE上测试出X性能 > Y
不代表在chrome上就会成立。

为了解决这一问题,jsPerf.com,这个网站诞生了,他其实也是run的是benchmark,
只是支持了不同的浏览器环境,还可以在线分享,累积测试,浏览器性能测试等。
至于jsPerf做测试以及在线分享,有兴趣的可以自己去学习一下。
这是我jsPerf的测试结果:https://jsperf.com/huangtengTest123
你可以把这个地址在不同浏览器打开测试性能。

现在你可以尽情的测试你以前写的代码了,也可以解答你的困惑,++a,a++到底谁更快,
我要提醒的是,不要沉迷于代码性能提升,要环境为主,人的感知,比如肉眼能感觉到的极限应该是100ms左右,也就是你的程序性能低于这个值当然牛逼,再继续提升性能就没有多大实际意义,因为反正人类已经感觉不出来了。

微性能

针对具体到某一段代码的优化,我是不可能举出所有列子的,在开发中应该注重代码测试,选择最好的性能代码。但是不要过度的执迷于代码的性能追求。

你写的代码在运行的时候,经过编译的几个步骤,打散成单元,重构AST,运行,可能就不是你写的那个样子了,所以探究是为了实际开发,不要钻牛角尖。

比如前面提到的类似的 –a 和 a– , 在汇编的层次,–a 是首选。但是你实际测试一下,会觉得差别微乎其微。
再比如一个字符串”42”转成数字类型再/2,有很多方法:

parseInt('42')/2;Number('42')/2;+'42'/2;('42'|0)/2'42'/2;...

哪一个更快?
其实这要结合实际,综合考量,比如 +’42’的方式快于parseInt,但是可读性差。

我们应该关注优化的大局,而不是担心这些微观性能的细微差别。

这里补充一个ES6的优化细节。CTO(“尾调用优化”)
就是指在一个函数(假设叫bar)的尾部调用某个函数(假设叫foo),而foo的尾部不会再调用其它函数了,而是直接返回一个结果或者别的。

ES6要求引擎实现了CTO,这样的好处是尾调用不会在bar里创建栈帧,这就节省了内存开销。
虽然看似没什么广泛的用途,但是在递归里面,你可以脑补,简直美如画啊。

有关js性能优化就到这里。

0 0
原创粉丝点击