Node.js里的性能杀手和终极解决方案

Posted 技术风向标

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Node.js里的性能杀手和终极解决方案相关的知识,希望对你有一定的参考价值。

node里有不少用法是性能杀手,用得不好的话会导致app性能下降,大大拖你的node app的后腿。然而优化Node App的终极大招也是最简单直接的方法,其实就是升级Node.js. 


要知道,如果某个方法里包含有不可优化的模式,即便它很常见,javascript引擎也不会去优化它。


下图总结了一些常见的用法在Node.js各个版本里是否能被优化:



可以看出,有些模式随着Node.js版本的升级,其性能也在优化,不再是性能杀手。

下面我们来逐个过一遍表里的这些模式,每个模式都有:

  1. 代码示例;

  2. 对该语言特征/模式的简短描述,并尽量解释为什么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%

Vue.js学习—— vue-cli初始化项目的坑终极解决办法和总结(离线安装webpack下载模板)

7个杀手级的开源监测工具

Node.js 性能优化

爬虫实战:爬虫之 web 自动化终极杀手 ( 上)

启用 Node.js 编码辅助 WebStorm 不起作用