Node.js V8

Posted by hubcarl on 17-08-12

Node.js v8.0.0 带来了哪些主要新特性

版本

  • V8 5.9 2017年4月

  • V8 5.9 版本之前是把JS编译成机器码(Crankshaft 的编译器), V8 5.9 版本开始,JS编译成字节码(Ignition+TurboFan 字节码解释器将默认启动, 字节码解释器 + JIT 编译器)

目的:

  1. 减轻机器码占用的内存空间,即牺牲时间换空间
  2. 提高代码的启动速度
  3. 对 V8 的代码进行重构,降低 V8 的代码复杂度

JS编译成机器码存在的问题

https://zhuanlan.zhihu.com/p/26669846

v8 将 JS 代码编译成机器码所带来的问题。因为机器码占空间很大,v8 没有办法把 Facebook 的所有 js 代码编译成机器码缓存下来,因为这样不仅缓存占用的内存、磁盘空间很大,而且退出 Chrome 再打开时序列化、反序列化缓存所花费的时间也很长,时间、空间成本都接受不了。

刚才提到了机器码占空间大的一个坏处,就是不能一次性编译全部的代码。机器码占空间大还有另外一个坏处,就是一些只运行一次的代码浪费了宝贵的内存资源。正如上面 Facebook 中的 __d() 系列函数,他们的作用可能只是注册、初始化各个模块组件,而一旦初始化完成便不会再执行。但由于机器码占空间大,这些只执行一次的代码也会在内存中长期存在、长期占用空间。正如下图所示,一般情况下大约 30% 的 V8 堆空间都用来存储未优化的机器码。

Ignition + TurboFan优化

Ignition解释器使用低级的、体系结构无关的TurboFan宏汇编指令为每个操作码生成字节码处理程序。TurboFan将这些指令编译成目标平台的代码,并在这个过程中执行低级的指令选择和机器寄存器分配。Ignition是一个寄存器机,每个字节码都将其输入和输出指定为显式寄存器寻址;它不是一个栈式机,每个字节码消费输入,并把输出推送到一个隐式栈上。

其实,Ignition + TurboFan 的组合,就是字节码解释器 + JIT 编译器的黄金组合。这一黄金组合在很多 JS 引擎中都有所使用,例如微软的 Chakra,它首先解释执行字节码,然后观察执行情况,如果发现热点代码,那么后台的 JIT 就把字节码编译成高效代码,之后便只执行高效代码而不再解释执行字节码。苹果公司的 SquirrelFish Extreme 也引入了 JIT。SpiderMonkey 更是如此,所有 JS 代码最初都是被解释器解释执行的,解释器同时收集执行信息,当它发现代码变热了之后,JaegerMonkey、IonMonkey 等 JIT 便登场,来编译生成高效的机器码

Node.js API (N-API)

对于使用或者开发原生插件的Node.js开发者来说,这个新的实验性的Node.js API(N-API)对于现有的Native Abstractions for Node.js (nan)是一个重大的进步。 它将会允许原生插件在系统上无需重复编译并且可跨多个不同版本的Node.js使用。 通过提供一个新的虚拟机不可知应用程序接口(ABI),原生插件不仅可以在多个版本的V8 javaScript运行,也可以在微软的Chakra-Core运行将成为可能。

N-API 目标:

  1. 有稳定的 ABI
  2. 抽象消除 Node.js 版本之间的接口差异
  3. 抽象消除 V8 版本之间的接口差异
  4. 抽象消除 V8 与其他 JS 引擎(如 ChakraCore)之间的接口差异

N-API 采取以下手段达到上述目标:

  1. 采用 C 语言头文件而不是 C++,消除 Name Mangling 以便最小化一个稳定的 ABI 接口
  2. 不使用 V8 的任何数据类型,所有 JavaScript 数据类型变成了不透明的 napi_value
  3. 重新设计了异常管理 API,所有 N-API 都返回 napi_status,通过统一的手段处理异常
  4. 重新了设计对象的生命周期 API,通过 napi_open_handle_scope 等 API 替代了 v8 的 Scope 设计

N-API 目前在 Node.js 8 仍是实验阶段的功能,需要配合命令行参数 –napi-modules 使用

经历过 Node.js 大版本升级的同学肯定会发现,每次升级后我们都得重新编译像 node-sass 这种用 C++ 写的扩展模块,否则会遇到下面这样的报错, Error: The module ‘…’ was compiled against a different Node.js version using NODE_MODULE_VERSION 51. This version of Node.js requires NODE_MODULE_VERSION 55. Please try re-compiling or re-installing the module (for instance, using npm rebuild or npm install).

NODE_MODULE_VERSION 是每一个 Node.js 版本内人为设定的数值,意思为 ABI 的版本号。一旦这个号码与已经编译好的二进制模块的号码不符,便判断为 ABI 不兼容,需要用户重新编译。

util.promisify

Nodejs 8 新增工具函数 util.promisify()将一个接收回调函数参数的函数转换成一个返回Promise的函数。

const util = require('util');
const fs = require('fs');
const writeFile = util.promisify(fs.writeFile);
const stat = util.promisify(fs.stat);

async function () {
  await writeFile('test.txt', 'async/await 与 util.promisify');
  const result = await stat('test.txt');
  console.log(result.size);
});

async_hooks

await/async 的异常处理多借助 try/catch 配合使用. 以前的 Node.js 版本中,try/catch 是个昂贵的操作,性能并不高。这主要是由于 v8 内老的 Crankshaft 不易优化这些 ES5 的新语法。但随着 TF+I 新架构的引入,try/catch 的写法也可以得到优化,作为用户就可以高枕无忧的使用 await/async + try/catch 了

这个实验性的async_hooks模块(以前叫async_wrap)在8.0.0版本中获得重大升级。该诊断API允许开发人员使用监视Node.js事件循环的操作,通过其完整的生命周期跟踪异步请求和处理。 该新模块完整的文档仍然不完整,用户在使用这个实验性的新模块时应格外小心。

升级npm到5.0.0

Node.js v8.0.0包含了最新的npm 5.0.0,其主要特性如下。

  1. 新的标准化文件锁机制。
  2. 不再需要–save,所有的安装都是默认保存的。
  3. node-gyp现在支持Windows。
  4. 新的发布包含SHA512和SHA1校验和检查。