新 V8 即将推出和 Node.js

Node.js 就依赖于 V8 引擎, V8 引擎是由 Google 为 Chrome 浏览器编写的 JavaScript 虚拟机。从一开始,V8 的主要目标就是使 JavaScript 运行更加快速,或至少比竞争对手快。对于高度动态这并不容易。这部分是关于 V8 和 JS 引擎性能的演变。

V8 引擎的核心部分是能够高速执行 JavaScript 的 JIT(Just In Time)编译器。这是一个动态编译器,可以在运行时优化代码。当 V8 最初构建时,JIT 编译器被称为 FullCodegen。然后,V8 团队实现了 Crankshaft,其中包括许多 FullCodegen 未实现的性能优化。

从 90 年代开始,JavaScript 哪些操作快,哪些操作慢(无论引擎是什么)通常都是反直觉的,所以JavaScript 代码慢的原因常常难以理解。

近年来,Matteo Collina 和我专注于如何编写性能优异的 Node.js 代码。当然,这意味着当 V8 引擎执行代码时,要知道哪些方法是快速的,哪些方法很慢。

现在是我们挑战所有关于性能假设的时候了,因为 V8 团队已经编写了一个新的 JIT 编译器:Turbofan。

从常见的“V8杀手”开始,到更复杂的 Matteo 和我对 Crankshaft 性能的发现,我们将会根据一系列测试结果来对 V8 进行评估。

当然,在优化 V8 代码之前,我们应该首先关注 API 设计,算法和数据结构(过早的优化是万恶之源)。下面会对 Node 的几个版本进行性能测试。我们可以得到一些结论来影响我们的代码风格,以及在应用通常的优化后提高性能的方式。

Try / Catch 问题

一个非常出名的去优化模式是使用 try/catch blocks.

测试代码链接(https://github.com/davidmarkclements/v8-perf/blob/master/bench/try-catch.js) 分别测试如下 4 种情况

函数中有 try/catch
函数中不带 try/catch
在 try 中调用函数
调用函数(没有 try/catch)

测试数据

我们可以看到 node 6 ( V8 5.1 ) 中 try/catch 的性能问题确实存在,但在 node 8 到 node 8.2 得到明显的改善。

另外值得注意的是, 在 try block 中调用函数明显比在 try block 外面要慢。

到 node 8.3 以上的版本 try/catch 对调用函数的影响基本可以忽略不计。

删除对象的属性

由于 javascript 对象的动态特性和原型链,导致属性查找更加复杂。

这里使用 3 种方式删除对象属性(测试代码https://github.com/davidmarkclements/v8-perf/blob/master/bench/property-removal.js)。

通过设置直接给对象属性设为 undefined
通过 delete 删除属性
删除最近添加的属性

在 Turbofan (V8 6.0 and 6.1)可以发现删除最近添加属性的效率更高,通过给对象属性直接设为 undefined 的效率也是非常高的。而 delete 操作在大部分情况表现不佳。

Arguments

普通的 JavaScript 函数可用隐式参数对象,它很像数组。

为了使用数组方法或大多数数组行为,参数对象的索引属性已被复制到数组中。在过去,JavaScript 中人们倾向于将较少的代码等同于与较快的代码。虽然这个经验法则为浏览器端代码带来了有效负载的好处,但同样的规则导致了服务器端代码大小远不如其执行速度重要的痛苦。所以将参数对象转换为数组的一种简洁方法就变得非常受欢迎:

Array.prototype.slice.call(arguments)。这调用 Array slice 方法传递参数对象作为该方法的上下文,slice 方法会得到一个像数组一样的对象,并相应地执行。也就是说,它将整个参数array-like 对象作为一个数组。

但是,当函数的隐式参数对象从函数上下文中暴露出来(例如,当从函数返回或传递到另一个函数中时,如 Array.prototype.slice.call(arguments)所示),这通常会导致性能下降。

测试代码(https://github.com/davidmarkclements/v8-perf/blob/master/bench/arguments.js)

暴露 arguments 对象给其他的函数
使用 Array.prototype.slice 复制 arguments 对象
使用循环复制对象的每一个属性
使用 es6 的展开操作符

我们再把这些数据转化成折线图:

如果我们想要将函数输入的参数写成一个数组,在 Node 8.3 以上版本我们建议使用 spread 操作符。在 Node 8.2 及以下版本我们最好通过循环复制对象属性到一个新的数组以提升性能。

对于 node 8.3 及以上版本,将参数对象直接暴露给其他的函数的情况性能表现很不错,因此在不需要把参数写成数组,这种方式可以大大提升代码执行效率。

原文地址:https://www.cnblogs.com/fenglee/p/7340580.html