Node.js编程之路之——与V8引擎共舞
发布于 3 年前 作者 classfellow 5061 次浏览 来自 分享

本文系原创,转载请注明出处~ 本文介绍V8和涉及到的主要概念。了解V8的特性,有助于提升代码性能。

Node.js可以看作是javascritp的运行时环境(JsRE),一方面,它提供了多种可调用的例如读写文件,网络请求,系统信息等API。另一方面,因为CPU执行的是一系列的机器码,它还负责将js代码解释成机器码执行,这部分工作是由V8引擎完成。V8是node的心脏,其诞生之初的目标,就是为了提高js的执行效率,它甚至直接将js编译为本地机器码,以节省一般脚本程序解释执行的时间。

V8与JIT

V8采用JIT——即时编译技术,直接将js代码编译成本地平台的机器码。宏观看,其步骤为Js源码—>抽象语法树—>本地机器码,并且后一个步骤只依赖前一个步骤。这与Java非常不同,Java将源码编译成字节码,然后给JVM解析执行,JVM根据优化策略,运行过程中有选择的将一部分字节码编译成本地机器码。V8的这个策略,往好处说,不生成中间的字节码,省去了两个步骤的时间,一个是从抽象语法树生成字节码的时间,一个是运用JIT从字节码生成本地机器码的时间。但字节码作为一种中间形式的存在,其意义在于可以使用现有的理论和技术手段做代码优化,而直接编译成本地机器码,有可能减少优化的机会。可以说V8是最早采取这种执行方式的Js引擎。 witch61截图20160609121852967.jpg V8的设计之初,是为了给chrome提升网页的执行效率,当网页加载完成,V8一步到位,编译成机器码CPU就开始执行了。试想不这样,而是再来个中间过程,好不容易编译成字节码,还不是直接能给CPU执行的东西,得边解释边执行,要是再来个中间码优化,V8那边儿已经开始跑程序了,这边儿呢,还在优化。对浏览器来讲,优化也没啥意义,因为优化需要的时间,可能程序就能跑的差不多了。本来一千多买个空调挺便宜,多花几千块买个节能空调,它确实省电费。但多花的3千块,够20年的电费了。

V8的优化策略

V8引擎的设计为Js代码的优化提供了新的思路和动力。就目前来看,V8使用了激进的优化策略,V8会启动一个数据分析器的线程,采集js代码运行数据,根据这些数据,决定是否需要对相关函数进行优化,以生成更高效代码。V8优化的激进表现在,针对热点函数,V8的策略较为乐观,倾向于认为此函数比较稳定,类型已经确定,于是大胆优化,生成相应的机器码。后面的执行中,万一遇到类型变化,V8采取叫做优化回退的方式,将js函数回退到优化前的较一般的情况。回退过程本身耗时,且放弃了优化后的代码去执行一般形式的代码,因此尽量不要触发。也就是,虽然js是动态类型的脚本语言,如果能够心里有数,别搞的过于动态,能够帮助自己的代码更高效的运行,这在服务器领域还是有意义的。

V8与隐藏类和内嵌缓存

如果说V8考虑到其运行环境和使用场景,最先使用了JIT技术,对隐藏类的概念和应用可以说曾是它的独门绝技。类是个所有人都熟悉的概念,加上隐藏两字,显得不觉明厉。什么是不隐藏类呢?不隐藏类就是C++/Java这种静态类型语言中,程序猿定义的类,黒纸白字明晃晃写在那的类。这些静态类型语言的每一个变量,都有一个唯一确定的类型。因为有类型信息,成员变量在对象中的偏移量在编译阶段就可确定,执行时CPU只需要用对象首地址——在C++是this指针,加上成员在对象内部的偏移量即可访内部成员。但对于Js这种动态语言,变量在运行时可以由不同类型的对象赋值,访问对象属性需要的信息完全由运行时决议,简单的按照首地址加偏移量的方式访问变量就行不通了。V8’悄悄的‘给运行中的对象分了类,在这个过程中产生了一种V8内部的数据结构——隐藏类。

给对象分类的好处是能够按照偏移量存取对象中的属性。V8把具有相同属性名称的对象归类为一组,因为这些对象的属性名一致,因此有相同的类描述,这些对象都会拥有同一个隐藏类。这个隐藏类包含一组对象内部的属性名和偏移量,它被组内的对象共享,因为是V8内部创建的,所以称其为隐藏类。

function Person(name, age){
	this.name = name
	this.age = age
}							
var p1 = new Person('lili', 0)
var p2 = new Person('una', 1)

p1,p2 引用的两个对象有同一个隐藏类,这个隐藏类记录了变量名和变量偏移,以后访问属性时,就可以按照对象首地址和偏移量访问属性。

V8与内嵌缓存

其它的JavaScript引擎和V8不同,它们将对象属性储存在哈希表中,而V8则将它们储存在数组中。偏移量信息——一个属性在数组中的位置—是储存在隐藏类的哈希表中。同一隐藏类的对象具有相同的属性名称。找到隐藏类,那么就可以得到偏移量然后在数组中存取属性。这比直接搜寻哈希表快许多。 内嵌缓存的思想是,缓存查找偏移量的结果,当下次使用属于相同隐藏类的对象时,因为要查找的隐藏类和被缓存的一致,可直接使用上次缓存的值。这样做减少了查表时间。但如果变量指向的对象类型变来变去,则会降低命中率。 一下这段代码描述了这个过程:

Object* find_name_value(Person* p) {
    if(cached == p->get_class()) {
      //命中,内联处理实际搜寻
      return p->properties[0]; 
    }else{
      return p->get_class()->lookup_attr("name");
    }
}

总结: 本文对Node的核心V8引擎以及其特性做了一个介绍,了解V8有助于写出更具亲和性的代码。V8本身的优化策略,使得具有固定类型编码风格的代码运行更加高效。

上一篇——generator 下一篇 Node.js高性能编程之—内存控制与Stream

1 回复

赞。 js代码经过V8后已经和C++执行速度不相上下了。

回到顶部