Node.js里的性能杀手和终极解决方案
Posted 技术风向标
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js里的性能杀手和终极解决方案相关的知识,希望对你有一定的参考价值。
node里有不少用法是性能杀手,用得不好的话会导致app性能下降,大大拖你的node app的后腿。然而优化Node App的终极大招也是最简单直接的方法,其实就是升级Node.js.
要知道,如果某个方法里包含有不可优化的模式,即便它很常见,javascript引擎也不会去优化它。
下图总结了一些常见的用法在Node.js各个版本里是否能被优化:
可以看出,有些模式随着Node.js版本的升级,其性能也在优化,不再是性能杀手。
下面我们来逐个过一遍表里的这些模式,每个模式都有:
代码示例;
对该语言特征/模式的简短描述,并尽量解释为什么V8无法对它进行优化。
Rest参数
function exec(...rest) {
return rest[0];
}
Rest参数在ECMAScript 2015规范中引入,作为arguments 对象的辅助。
目前,V8不会对使用了这个功能的方法做优化。
访问并不存在的参数索引
function exec(/* no arguments */) {
var arg = arguments[1];
}
arguments对象在ECMAScript第一版引入,用于向函数签名中增加多态。
就我所知,目前在V8中只有两种arguments的用法不是性能杀手。
调试声明
function exec() {
debugger;
}
调试声明(debugger statement)在ECMAScript规范的第三版中正式引入。使用之后可能会改变脚本的执行工作流程。
对使用了调试声明的函数进行优化的过程非常复杂。 不过无论如何,生产代码中是不应该有任何调试语句的。
关键字在循环外部的'for...in'循环
function exec() {
var obj = {};
for (key in obj) {}
}
for...in循环把V8团队折磨得很惨,因为他们为此专门在blog里写了文章Fast For-In in V8:
https://v8project.blogspot.fr/2017/03/fast-for-in-in-v8.html
闭包导致arguments的泄漏
function exec() {
var args = arguments;
return function() {
return args;
}
}
Bluebird wiki里有篇很好的文章专门针对arguments泄漏问题:
https://github.com/petkaantonov/bluebird/wiki/Optimization-killers#32-leaking-arguments
对象字面量中有getter或者setter
function exec() {
var obj = {
get prop() {
return 1;
}
};
}
function exec() {
var obj = {
set prop(value) {
}
};
}
如果能访问对象的属性,ECMAScript可以用一种强大的方式来执行代码。但是这可能会对对象的成员类型引入多态。
用arguments重写命名函数的参数
function exec(arg0) {
var foo = arguments[0];
arg0 = 'foo';
}
再说一次,这就是上文提到的“用arguments”。V8不喜欢你们把arguments弄得乱七八糟。
把arguments传给call()
function exec() {
Array.prototype.slice.call(arguments);
}
如果你想把某个函数的参数从一个方法传给另一个方法,千万不要用Function.prototype.call,而要用Function.prototype.apply。因为apply把类Array对象当作第二个参数,你可以直接把参数对象传给它:myFunction.apply(null, arguments)
对arguments和对const/let变量的phi用法
function test1() {
var _arguments = arguments;
if (0 === 0) { // anything evaluating to true, except a number or `true`
_arguments = [0]; // Unsupported phi use of arguments
}
}
function test() {
for (let i = 0; i < 0; i++) {
const x = __lookupGetter__; // `__lookupGetter__` and
}
const self = this; // `this` should both be present for this to happen
}
返回arguments
function exec() {
return arguments;
}
这就是所谓的arguments泄漏,几乎不可能对这样的函数进行优化。
Yield
function* exec() {
yield 0;
}
ECMAScript 2016中引入了Generators。对V8来说这种对象的行为很奇怪,因为这些函数可以先退出再重新进来。
目前V8不对generator做优化。
给arguments对象赋值
function exec() {
arguments = 'foo';
}
在以前,这种模式是不会被优化的,跟上文提到的原因一样,这也是因为用了arguments对象。
不过好像最新版已经可以优化它了。
调用`eval()`
function exec() {
eval('var foo = 5;');
}
同样,这样的用法以前是不会被优化的。eval是一个自带黑魔法的函数,它计算JavaScript字符串,把它作为脚本代码来执行并且返回字符串。
这决定了它的不稳定的性质,但最新版引擎也会优化它,这得多谢它的新架构:TurboFan
`try…catch…finally`/`try…finally`/ `try…catch`
function exec() {
try {} catch (e) {} finally {}
}
view rawtry-catch-finally.js hosted with ❤ by GitHub
function exec() {
try {} finally {}
}
view rawtry-finally.js hosted with ❤ by GitHub
function exec() {
try {} catch (e) {}
}
这可以算是V8对常用结构最酷的一次强化了,可能还是得归功于它的新的优化编译器TurboFan。
用Function.prototype.bind()绑定函数
var exec = function() {};
exec.bind(null)();
Function.prototype.bind在ECMAScript 5.1中引入,通过将上下文(this和arguments)与另一个函数绑定的方式,实现用一个函数创建另一个新函数。
Benedikt Meurer针对这个话题写了一篇好文章:
http://benediktmeurer.de/2016/01/14/optimizing-bound-functions-further/
数组为参数的`for…in`循环
function exec() {
var arr = [1, 2];
for (var key in arr) {}
}
前面曾经说过,for...in循环让V8团队非常头疼,我再一次建议你们读一读他们的博客文章:
https://v8project.blogspot.fr/2017/03/fast-for-in-in-v8.html
超过128个case的`switch`语句
function exec(arg) {
switch (arg) {
case 1: break;
case 2: break;
...
case 128: break;
case 129: break;
}
}
以前,这样臃肿的switch是不会被优化的,不过V8团队已经解除了这个限制,现在已经可以被优化了。
总的来看,升级node.js之后会对你的node app有性能提升,而 要想彻底优化你的node应用,还得对V8 JavaScript引擎有深入的了解。
推荐阅读
.
以上是关于Node.js里的性能杀手和终极解决方案的主要内容,如果未能解决你的问题,请参考以下文章
ThinkJS 2.1:支持 TypeScript,性能提升 90%