认识 V8 引擎( 三 )

  • 偏移信息共享,C++有类型定义,执行时不能动态改变,可共享偏移信息,JavaScript每个对象都是自描述,属性和位置偏移信息都包含在自身的结构中;
  • 偏移信息查找,C++查找偏移地址很简单,在编译代码阶段,对使用的某类型成员变量直接设置偏移位置,JavaScript中使用一个对象,需要通过属性名匹配才能找到相应的值,需要更多的操作 。
  • 在代码执行过程中,变量的存取是非常普遍和频繁的,通过偏移量来存取,使用少数两个汇编指令就能完成,如果通过属性名匹配则需要更多的汇编指令,也需要更多的内存空间 。示例如下:
    认识 V8 引擎

    文章插图
     
    在JavaScript中,除boolean,number,string,null,undefined这个五个简单变量外,其他的数据都是对象,V8使用一种特殊的方式来表示它们,进而优化JavaScript的内部表示问题 。
    在V8中,数据的内部表示由数据的实际内容和数据的句柄构成 。数据的实际内容是变长的,类型也是不同的;句柄固定大小,包含指向数据的指针 。这种设计可以方便V8进行垃圾回收和移动数据内容,如果直接使用指针的话就会出问题或者需要更大的开销,使用句柄的话,只需修改句柄中的指针即可,使用者使用的还是句柄,指针改动是对使用者透明的 。
    除少数数据(如整型数据)由handle本身存储外,其他内容限于句柄大小和变长等原因,都存储在堆中 。整数直接从value中取值,然后使用一个指针指向它,可以减少内存的占用并提高访问速度 。一个句柄对象的大小是4字节(32位设备)或者8字节(64位设备),而在JavaScriptCore中,使用的8个字节表示句柄 。在堆中存放的对象都是4字节对齐的,所以它们指针的后两位是不需要的,V8用这两位表示数据的类型,00为整数,01为其他 。
    JavaScript对象在V8中的实现包含三个部分:隐藏类指针,这是v8为JavaScript对象创建的隐藏类;属性值表指针,指向该对象包含的属性值;元素表指针,指向该对象包含的属性 。
    2.2.工作过程前面有过介绍,V8引擎在执行JavaScript的过程中,主要有两个阶段:编译和运行,与C++的执行前完全编译不同的是,JavaScript需要在用户使用时完成编译和执行 。在V8中,JavaScript相关代码并非一下完成编译的,而是在某些代码需要执行时,才会进行编译,这就提高了响应时间,减少了时间开销 。在V8引擎中,源代码先被解析器转变为抽象语法树(AST),然后使用JIT编译器的全代码生成器从AST直接生成本地可执行代码 。这个过程不同于JAVA先生成字节码或中间表示,减少了AST到字节码的转换时间,提高了代码的执行速度 。但由于缺少了转换为字节码这一中间过程,也就减少了优化代码的机会 。
    V8引擎编译本地代码时使用的主要类如下所示:
    • Script:表示JavaScript代码,即包含源代码,又包含编译之后生成的本地代码,即是编译入口,又是运行入口;
    • Compiler:编译器类,辅组Script类来编译生成代码,调用解释器(Parser)来生成AST和全代码生成器,将AST转变为本地代码;
    • AstNode:抽象语法树节点类,是其他所有节点的基类,包含非常多的子类,后面会针对不同的子类生成不同的本地代码;
    • AstVisitor:抽象语法树的访问者类,主要用来遍历异构的抽象语法树;
    • FullCodeGenerator:AstVisitor类的子类,通过遍历AST来为JavaScript生成本地可执行代码 。

    认识 V8 引擎

    文章插图
     
    JavaScript代码编译的过程大致为:Script类调用Compiler类的Compile函数为其生成本地代码 。Compile函数先使用Parser类生成AST,再使用FullCodeGenerator类来生成本地代码 。本地代码与具体的硬件平台密切相关,FullCodeGenerator使用多个后端来生成与平台相匹配的本地汇编代码 。由于FullCodeGenerator通过遍历AST来为每个节点生成相应的汇编代码,缺失了全局视图,节点之间的优化也就无从谈起 。
    在执行编译之前,V8会构建众多全局对象并加载一些内置的库(如math库),来构建一个运行环境 。而且在JavaScript源代码中,并非所有的函数都被编译生成本地代码,而是延迟编译,在调用时才会编译 。
    由于V8缺少了生成中间代码这一环节,缺少了必要的优化,为了提升性能,V8会在生成本地代码后,使用数据分析器(profiler)采集一些信息,然后根据这些数据将本地代码进行优化,生成更高效的本地代码,这是一个逐步改进的过程 。同时,当发现优化后代码的性能还不如未优化的代码,V8将退回原来的代码,也就是优化回滚 。下面介绍一下运行阶段,该阶段使用的主要类如下所示:


    推荐阅读