【NodeJS】对于V8引擎的一点认识..

本是古典 何须时尚 2022-06-04 10:24 349阅读 0赞

JavaScript代码的编译与优化

  Node可以看作是JavaScript的运行时环境。一方面,它提供了多种可调用的API,如读写文件、网络请求、系统信息等。另一方面,因为CPU执行的是机器码,它还负责将JavaScript代码解释成机器指令序列执行,这部分工作是由V8引擎完成。

即时编译

  V8采用即时编译技术(JIT),直接将JavaScript代码编译成本地平台的机器码。宏观上看,其步骤为JavaScript源码—>抽象语法树—>本地机器码,并且后一个步骤只依赖前一个步骤。
  这与其他解释器不同,例如Java语言需要先将源码编译成字节码,然后给JVM解释执行,JVM根据优化策略,运行过程中有选择地将一部分字节码编译成本地机器码。
  V8不生成中间代码,一步到位,编译成机器码,CPU就开始执行了。比起生成中间码解释执行的方式,V8的策略省去了一个步骤,程序会更早地开始运行。并且执行编译好的机器指令,也比解释执行中间码的速度更快。不足的是,缺少字节码这个中间表示,使得代码优化变得更困难。

隐藏类

  首先我们看一下C++/Java这种静态类型语言的每一个变量,都有一个唯一确定的类型。因为有类型信息,一个对象包含哪些成员和这些成员在对象中的偏移量等信息,编译阶段就可确定,执行时CPU只需要用对象首地址 —— 在C++中是this指针,加上成员在对象内部的偏移量即可访问内部成员。这些访问指令在编译阶段就生成了。
  但对于JavaScript这种动态语言,变量在运行时可以随时由不同类型的对象赋值,并且对象本身可以随时添加删除成员。访问对象属性需要的信息完全由运行时决定。为了实现按照索引的方式访问成员,V8“悄悄地”给运行中的对象分了类,在这个过程中产生了一种V8内部的数据结构,即隐藏类。隐藏类本身是一个对象。
  隐藏类起到给对象分组的作用。同一组的对象,具有相同的成员名称。隐藏类记录了成员名称和偏移量,根据这些信息,V8能够按照对象首地址+偏移量访问成员变量。

内联缓存

  上面讲到,借助隐藏类,可以使用数组索引的方式存取对象成员。但成员的索引值是以哈希表的方式存储在隐藏类中。如果每次访问属性都搜寻隐藏类的哈希表,那么这种使用偏移量的方式不会带来任何好处。
  内敛缓存是基于程序运行的局部性原理,动态生成使用索引查找的代码。下一次存取成员变量就不必再去搜寻哈希表。

优化回退

  V8 为了进一步提升JavaScript代码的执行效率,使用了Crankshaft编译器生成更高效的机器码。程序在运行时,V8会采集JavaScript代码运行数据。当V8发现某函数执行频繁,就将其标记为热点函数。针对热点函数,V8的策略较为乐观,倾向于认为此函数比较稳定,类型已经确定,于是调用Crankshaft编译器,生成更高效的机器码。后面的运行中,万一遇到类型变化,V8采取将JavaScript函数回退到优化前的较一般的情况。

  1. function add(a, b){
  2. return a + b
  3. }
  4. for(var i=0; i<10000; ++i){ add(i, i); }
  5. add('a', 'b');

  上述代码在执行for循环的过程中,每次调用add()函数,传入的参数是整型,运行一定次数后,V8可能把这个函数标记为热点函数,并根据每次运行传入的参数预测,此函数的参数a、b为整型。于是调用Crankshaft编译器生成相应的代码。但当循环退出,执行字符串想加时,V8只好将函数回退到一般状态。回退过程就是根据函数源码,生成相应的语法时,然后编译成一般形式的机器码。可以预见这个过程是比较耗时的,并且放弃了优化后的代码去执行一般形式的代码,因此要尽量避免触发。

那么再来看一个例子:

  1. // 片段 1
  2. var person = {
  3. add: function(a, b){
  4. return a + b;
  5. }
  6. };
  7. obj.name = 'li';
  8. // 片段 2
  9. var person = {
  10. add: function(a, b){
  11. return a + b;
  12. },
  13. name: 'li'
  14. };

  以上代码实现的功能相同,都是定义了一个对象,这个对象具有一个属性name和一个方法add()。但使用片段2的方式效率更高。片段1给对象obj添加了一个属性name,这会造成隐藏类的派生。给对象动态地添加和删除属性都会派生新的隐藏类。假如对象的add函数已经被优化,生成了更高效的代码,则因为添加或删除属性,这个改变后的对象无法使用优化后的代码。
  上面的优化回退的例子也启示我们,函数内部的参数类型越确定,V8越能够生成优化后的代码。我们也要避免优化回退,例如可以再编写一个专门针对字符串想加的函数,而不是一个函数同时处理整型和字符串。

发表评论

表情:
评论列表 (有 0 条评论,349人围观)

还没有评论,来说两句吧...

相关阅读

    相关 V8引擎内存收回

    V8引擎的内存收回 前言 JS语言不像C/C++, 让程序员自己去开辟或者释放内存,而是类似Java,采用自己的一套垃圾回收算法进行自动的内存管理。 我们知

    相关 对于oracle sql左连接一点认识

    我们在连表的时候,可能有这样的需求,比如我要查询歌曲的信息,有张Song表,但是我需要返回的额数据不仅仅是Song的信息,可能还有专辑的信息,这时候我们就需要使用到左连接。左连

    相关 对于Java认识

    1.运行于多个平台,如Windows, Mac OS,及其他多种UNIX版本的系统 Java编译器将 java 源文件编译为 class 字节码文件 Java分为三个体

    相关 v8引擎详解

    前言 `JavaScript`绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(`NodeJs`),更是爆发了极强的生命力。编程语言分为编译型语言和解释