chrome-v8-js引擎源码导读分析


v8 第一章 v8 之整体流程 第二章 v8 之全局环境配置及初始化 2.1 全局模板 2.2 库函数 2.3 初始化 第三章 v8 之前端建立语法树 3.1 v8编译中重要的类 3.2 compile 之前的查找 3.3 建立语法树 第四章 v8 之后端全代码生成 (full codegenerator) 第五章 v8 之后端优化代码生成 (crankshaft) 5.1 调用 crankshaft 的条件 5.2 Hydrogen 5.3 Lithium 5.4 寄存器分配 第六章 v8 之运行时监听 (runtime profile) 第七章 v8 之LazyCompiler 第八章 v8 之inline cache 8.1 前提条件及实现方式 8.2 stubs 函数 第九章 v8之性能分析 (各流程所占时间比 ) 第一章 v8之整体流程 1.)v8在进入 main 函数之后首先做的就是参数分析 ,根据参数设 置Flags。 2.)然后创建一个基于栈分配的HandleScope(在这里须得说明在 v8中任何一个对象都是需要handle 来指向的,如果没有则这个对象 将很快被垃圾回收器回收掉 。对象的释放意味做 handle 将没有用 ,因 此在每一个v8逻辑层次中都有一个HandleScope 来管理该层次中的 所有handle。释放一个scope, 则这个scope 中的所以handle 就被释放 掉了 )。 3.)创建一个新的执行环境(即为v8执行js的环境,在创建该全 局执行环境的同时,创建全局的对象模板,函数模板,编译built_in function(详细情况请见第二章v8之环境配置及初始化)。进入该新建 的全局执行环境中(所谓进入该环境变量就是设置当前isolate 中执行 的环境变量 )。 4.)然后就是编译执行 *.js 用户代码。 a) 首先进行语法分析,建立语法树。 b) 首次执行采用fullCodegenerator 编译最外层框架代码,并开始 执行。 c) 运行遇到还没有编译的function 代码采用 RuntimeLazyCompile,并用inline cache 技术将其他同名的对 象指向该编译的代码。 d) 若在运行到该同名对象时发生CacheMiss(调用不匹配),则 采用ICMissLazyCompile 对调用的对象或函数进行编译(当然 这里还有其他IC处理情况出现,比如ICCompare 等等stubs function 的处理 )。 e) 在整个代码运行处理的过程中,v8还会创建一个监听线程 (profile thread),该线程监听function 的运行情况,收集类型信 息,记录运行次数 ,记录 function 编译后的代码量等等 ,判断 该function 是否为hot function。(运行次数>2, 类型信息收集 比例>15%,代码量是否有<5*144,是否发生IC_changed),达 到要求后标记该函数为可优化,下次运行的时候采用 RuntimeLazyRecompile,利用Crankshaft 优化代码,并替换原 来的代码。同时还有监听优化过的代码判断其是否优化过头 , 是否需要 deoptimal。 f) 在产生的优化代码中常常还要用到on-stack replacement(该技 术为不中断程序继续进行的情况下,进行代码替换 )。 g) 在Crankshaft 生成优化代码的过程中,需要先建立语法树 ,进 行一次全代码生成 ,模拟该代码运行 ,判断分析哪些数据分配 到栈上 ,哪些变量分配到堆上 ,变量类型绑定等等 ,利用同一 个语法树生成静态单赋值表示的中间表示代码Hydrogen(构建 图),同时将function 可能调用的代码进行inline(有充足类型信 息的被调函数 ),然后优化图 (循环不变量外移 ,公共子表达式 消除 )。遍历图生成三地址形式的中间表示 Lithium ,进行寄存 器分配,最后生成本地代码。 5.)在整个过程中垃圾回收器都紧紧的追踪 v8中所有的对象 ,参 看其是否still alive, 将其分类进行处理。在进行垃圾回收的时候要中 断程序的进行,每次只处理要回收的一部分。 第二章 v8 之全局环境配置及初始化 2.1 全局模板 v8在运行时创建对象,函数都需要调用具体的模板方法来实现 。 在v8 中有2个非常重要和常用的模板,分别是objectTemplate 和 functionTemplate。 objectTemplate 用来在运行时创建对象,向一个objectTemplate 中 添加properties,就是向所有以该对象模板创建的对象中添加这些属 性。继承自 Template(继承自 data)。Frend class functionTemplate。 functionTemplate 用来在运行时创建函数,在一个context 中一个 functionTemplate 只能创建一个函数,该函数的生存周期和context 的 生存周期相同 。每个函数模板可以有属性 ,并且这些属性会在创建函 数的时候添加到该函数中。每个函数模板都有一个对应的实例模板 , 用来以该函数为构造器创建对象实例。每个函数模板还有一个 prototype 模板,用来创建函数的原型对象。functionTemplate 的具体 使用请参见include/v8.h line 2109。另外functionTemplate 还可以继 承自另外一个函数模板,子函数模板继承父模板的属性,并且通过 __proto__可以访问父模板的原型对象。functionTemplate 继承自 Template, friend class ObjectTemplate。 2.2 库函数 在v8中库函数时用javaScript 实现的,这样使得库函数的更新, 修改更加方便。在runtime.js 中,若调用这些库函数将会采用 lazyCompile 的方式进行编译。 2.3 初始化 v8 的初始化代码也是使用*.js 实现的,在apinatives.js 中调用 Instantiates 函数,对函数,模板,模板实例进行初始化工作。这些js 代码将会在isolate->bootstrpper()->CreateEnvironment(...)中的Gensis 函数中进行编译成本地代码。这些初始化的作用就是实现函数模板 , 对象模板。当然在初始化的过程中会调用到库函数,调用时将采用 lazycompile 的方式进行编译。 初始化的作用其实是构造一个运行环境,在这个环境中一切为 javascript 所需的基本框架将会得到建立,包括库函数,js的模板,对 象等。而这些*.js 处理的方式和整个v8对待用户的js程序是基本一 致的,这些built_in js 函数都过Genesis::ConfigureApiObject 再调用 Execution::Call 函数实现编译执行的。 第三章 v8 之建立语法树 3.1 v8 编译中重要的类 CompilerInfo: 封装一些在编译时间知道的信息,根据 compiler-time 时有的资源进行构造,需要用到ScriptDataImpl 中的信 息。 CompilerInfoWithZone:和CompilerInfo 类似,只是在构造的时候 需要申请一 块zone, 并且进入 该zone, 在改累的对 象exit 的时候会释 放这块 zone。Zone 的作用就是快速的进行小块的内存分配 。Zone 用 来保存临时的数据结构 ,比如说抽象语法树 ,抽象语法树会在编译结 束后被释放掉。 OptimizingCompiler: 在chrankshaft 优化编译的三个阶段跟踪保存 状态(三个阶段分别是:建图,图优化, code 生成和 install)。 Compiler: v8的编译类,编译的基本策略就是将sourc code 编译成 匿名函数,给予参数就可被执行。如果这个source code 包含其他函 数(调用其他函数), 这些函数将会被编译,并且分配到该source code 中(作为其的一部分 )。 CompilationCache: 保存编译过的script 和evals 的shared function infos。这些共享的函数信息通过 source string 作为关键来查找。 3.2 compile 之前的查找 Compile cache 包含几层sub-caches,每个子cache 针对每代 sub-cache 包 含一个对应 的compilation cache tables。因为对scripts 和evals 同样的source code string 将会产生不同的编译代码,v8 应用不同的 sub-caches 来保存不同编译 modes 产生的代码 ,来防 止检索到错误结果。 这些编译结果子cache, 都继承自CompilationSubCache 。 CompileCacheScript 是专门保存scripts 编译结果的子cache。 CompileCacheEval 是专门保存 eval script 编译结果的子 cache。 CompileCacheRegExp 是专门保存正则表达式编译结果的子 cache。 v8 在对任一 source code 进行编译生成语法树之前 ,先要查找 相应的 compile cache,看该源代码是否已经编译过了 ,如果已经 编译过了,可能就不需要再次编译了,直接导入就可以了。 在源代码执行到Compile::Compile(...) 之时,首先通过 compilation_cache->lookupScript 查看当前的源代码是否已经编 译过 ,存放在 cache 中了 ,如果有 ,则直接导出就可 。如果没有 则要编译,且编译结束后要把结果存放在想对应的 cache 中。 3.3 建立语法树 在查找无结果之后,v8 将对源代码进行分析,建立语法树。 v8 在进行一系列环境配置之后( Scope, zone 等配置 ),通过扫描 器scanner,将源代码继续分析归类 。分为语句 (statement),和函 数声明(functionDeclaration ),在harmony mode 下还允许有 LetDeclaration ,ConstDeclaration ,ModuleDeclaration , ImportDeclartion,ExportDeclaration 等模块元素。 Parser::ParseStatement(...)是 语法分析的入口 ,在 该函数里进行 实质的语法分析。它是一个分拣器,分析出每个 js 语句的类型 , 然后针对不同的类型再进一步调用相关的分析函数 。具体的流程 为:首先分离出语句的关键字,因为在scanner 处理后每个语句 都会有相应的关键字指代 ,有很清楚的含义 。比如在 js 中声明变 量var value; statement 分析器将会将该变量声明语句传 给Block* ParseVariableStatement()方法进行分析 ,并将结果放入 zone 中, 并插入到 AST 树中的相应位置。 v8 在语法分析时将语句分为bolck,变量语句,空语句,表达 式语句 ,if 语句 ,Iteration 语句 ,continue 语句 ,break 语句 ,return 语句,with 语句,labelled 语句(这里须得注意,labels 只会在 break,continue 语句中被使用,而这两种语句只有在 blocks,iteration,和switch 语句中才合法。因此在其他语句中的 labels 可以简单的忽略掉 。),switch 语句 ,throw 语句 ,try 语句 , debugger 语句。 每一种类型的statement 的处理方式基本类似,都是按照 Ecma262 第5版的标准进行分析的。进行匹配类似的分析,在 递归的调用相应的处理方法。 在建立语法树中我们可以看到每个js 函数的语法树都是显示 函数名 ,及其引用明 ,然后便是函数中的变量声明 ,接着是 boby 模块,在 boby 模块中是 call, 赋值,初始化等等操作。 可以使用--print_ast 打印所以的语法树。使用--trace_parse 查 看分析语法树建立的过程和花销的时间。(时间比列请参见第十 章v8 之性能分析 ) 第四章 v8之后端全代码生成 (full codegenerator) v8在语法树生成之后将继续本地代码生成 ,在生成与平台有关的 本地代码之前,需要做一系列的工作(比如判断是否采用优化编译 , 是否是因优化过度导致代码效率反而不高需要去优化等等),在采用 代码生成的的首要条件就是判断源码是否语法编译正常 。代码生成的 后置条件便是生成的代码已经存放在 compilation info 中。 全代码生成(直接翻译语法树,不采用优化),在mips 中函数代码 首先做的事情就是将a1(被调js 函数本身),cp(被掉函数的上下 文),fp(调用者的frame 指针),sp, ra。在建立语法树一章中我们知道函 数语法树的结构特点。因此在全代码生成是首先便是局部变量分配 (对应语法树的变量声明 ),然后是分配局部环境,为参数等分配空间 , 然后进行栈检查,然后是对 body 进行处理,最后是返回值的处理。 从上的分析可以看出 v8在全代码生成是步骤大致是 :首先是保留 现场的入栈,局部变量入栈,局部环境保存,声明处理,栈检查 ,整 个函数 body 处理,返回值处理。 在对body 的处理中,首先调用的是ASTNode 的是Visitstatements 方法,针对每一个statements 调用相应的visit 方法。另外这些框架处 理都是平台无关的。 具体的实现就不在述了 ,比如 对if 语句首先判断是否有对应 的else 语句,分开处理这两种情况。先处理if中的then 语句然后处理跳转 的情况。 值得注意的是v8为函数调用,变量引用、保存等实现了inline cache 技术。因此在这些相应的地方要引入inline cache 的stub 函数。 具体情况请见第八章 v8之inline cache 可以通过--print_code ,--print_opt_code ,--print_all_code , --trace_codegen --trace_opt 等查看代码的生成情况,就输出产生的所 有代码。 第五章 v8之后端优化代码生成 (crankshaft) v8通过重新编译优化热点函数提供性能 ,而对一些特殊的 ,处理 次数少的函数进行全编译。Crankshaft 首先将javaScript 的抽象语法 树转换成高级的中间表示 Hydrogen(静态单赋值 SSA),然后试图优 化Hydrogen 图,优化后把 Hydrogen 表示转换成低级的与平台相关的 中间表示Lithium,该中间表示支持寄存器分配。最后再转换为本地 代码。 5.1 调用 crankshaft 的条件 在v8 编译js 函数时首先会在compile info 中标记该函数是否可以 被优化,然后再首次全编译的时候会通过PROFILE 来设置监听,有 监听线程查看其运行次数,和在inline cache mis, 与编译是收集的类 型信息等等来判断该函数是否为 hot function。具体的判断标准就是该 函数至少已经运行 2次了 ,收集的类型信息比列大于 15%,代码量 (不 包括函数运行时调用的stubs 函数)大于一个阈值(在mips 中设置为 5*144 性能比较好 )。还有就是是否发生过 IC_Changed(如果在本次运 行的时候发生了 cache mis,则不会标记为可优化 ,因为 v8认为如现在 还在发生mis,则可能该函数还不够稳定(即该函数要调用的许多分支 还不能确定 )。) 另外再运行时间很长的loop中crankshaft采用on-stack replacement 的方法进行运行时替换而不中断 loop 的运行。 5.2 Hydrogen v8 在优化编译时采用静态单赋值(SSA)作为其高级中间表示 称为 Hydrogen。由上面的分析我们已经知道在做优化时 v8 是先 做了次全代码生成来模拟运行查看代码的分配情况,运行环境 等。这些在 Hydrogen 的控制流分析中将起到十分重要的作用。 Hydrogen 主要是为了以下这三个方面 :首先就是 inline,web 中js 程序,或者一般的js 脚本的程序行为主要不是为了进行科 学计算 ,而是为了快速的时时的进行交互 ,传递消息 。这些行为 需要js 程序再运行的时候能进行快速的属性访问(如果将函数调 用也看着是某对象的属性访问的话 ),而javascript 是无类型的动 态语言 ,属性访问是其比较耗时的操作 ,因为这需要一大堆的查 找,类型确定等操作。因此这也是 js 引擎主要需要优化的地方 。 在v8 中的Hydrogen 能比较好的解决这些问题。Inline 的采用将 会带来许多其他优化的可能 。其次就是对待临时变量 ,Hydrogen 中将临时变量看做是untagged integer 或者直接看做是double value。最后就是建图进行控制流 ,数据流分析 ,支持循环不变量 外移,公共子表达式删除。 在Hydrogen 中最要的数据结构便是 HGraph,HBasicBlock, HPhi,HValue。在Hydrogen 中 每条语句称为一条 HInstruction。 另外我们知道静态单赋值中每个变量只能被赋值一次 ,因此出现 的变量较多 ,在v8 中便使用该被赋值变量所在的 HInstruction 的 地址作为其变量名,这样既解决变量命名的问题有提高了 HInstruction 的访问速度。 HValue 有两种值 ,一个就是 HInstruction,另一个就是 HPhi。 HPhi 用在控制流中。每个value 都有指向该value 所在block 的 指针和指向该value 被使用的地方的指针数组,这样使得value 的访问和使用非常快 ,是种优化编译速度的手段 ,其次保存了更 多的信息利于控制流和数据流分析。 Hydrogen 的流图是由 BasicBlocks 组成的 ,每个基本块都保存 了很多信息(保存前驱,后继,控制模块,指令,最后一条控制 指令:作用是将控制权沿控制流传给下一个 BasicBlock)。 现综述一下 Hydrogen 生成的过程 :首先 Crankshaft 像全代码 生成过程一样遍历抽象语法树 ,并且模拟全编译的过程 ,需要知 道哪些分配到栈上 ,哪些变量分配的堆上 ,命名局部变量 ,分配 HValue 去绑定这些变量。并将这些所有的信息都保存在 environment 中,还有一件重要的事情就是在转换过程中要进行 Phi 节点的插入,采用最简单的方式:如果一个基本块有一个以 上的前驱,变量可能来自多个value 则直接加上phi 指令。最后 会再剪枝。 在语法树中,每个节点都会被赋予一个整数标示符,这些标 示符是特定的(因此一颗语法树在全代码编译和Crankshaft 编译 中这些标示符都是一样的),而在全代码生成中使用inline cache 会将这些标示符嵌入进去。而inline cache 会在函数曾经用过的 地方 (call site),属性访问中记录类型 。这就使得 Crankshaft 可以 使用这些得到的类型信息 ,并用他们来初始 化HValue 的type 域。 该type 域可以用来帮助类型引用 ,inline 这些有确定类型的函数 或者方法调用。 Hydrogen 会在建图的过程中inline 函数调用,这样可以提高 代码移动的可能性 (v8 不需要函数代码间的移动 ),并为 inline 还 是订下了以下几点限制 :1.inline 函数的代码大小有限制 (mips 为 700 个字符),2.外层函数和被inline 的函数没有基于堆分配的变 量,3.对应 for-in、with 等表达式不进行 inline,4.inline 设置了嵌 套深度 ,5.不递归的 inline 自己 ,6.inline 会增加 AST 的节点数 , 而该节点数有限制。 建图完毕后就是对图进行优化 ,由上可以知道这个时候 inline 已经完成,首先要做的事情就是减少变量的个数(SSA 变量个数 较多),然后就是将BasicBlocks 进行top 排序,反复的以数据流 的方向进行遍历该序列 ,计算每个基本块的控制块 ,和后继模块 。 标记dead subgraph(一个函数被标记为可以优化,有可能其某些 分支并没有到达过 ,因此这些分支的类型信息会没有 ,在这中情 况下降插入软件去优化点 ,当以后运行到该分支时将产生去优化 例外 ,进行去优化重编译过程 )。然后要做的就是删除冗余的 Phi, 进行Phi 收 集,这里采用的是 naïve phi placement 算 法。其次做 的就是分析变量的生成周期 ,为后面三地址中间表示的寄存器分 配建立基础。 优化之后就是代码生成 (生成 Lithium 三地址中间表示 ,然后 本地代码)和安装,设置优化状态。可以通过--print_opt_code 打 印 优化代码, --trace_opt 追 踪优化状态,打印优化各阶段代码花 销的时间。 5.3 Lithium 在Crankshaft 将语法树转换成Hydrogen 后并不直接转换成本地代 码,而是经过一种类似于三地址表示的低级中间表示Lithium,再转 换成本地代码 ,Lithium 是与平台相关的中间表示 ,相对于 Hydrogen 它添加了Label,goto 等,并为操作数,结果进行了显示的命名(在 hydrogen 中使用的是Instruction 的地址作为临时变量的名字)。并在 已经完成的变量周期的情况下再次进行分析变量的使用周期 。最后进 行寄存器分配。 Gap:是一组寄存器移来移去的动作记录,在从Hydrogen 转换成 LIthium 过程中,在每条L指令后面都添加一条Lgap 指令。在寄存 器调度器工作之后记录这些寄存器的移动情况。 5.4 寄存器分配 v8 采用的线性扫描的寄存器分配算法 。先进行深度优先排序 , 确定活跃区间 。由数据流扫描一遍中间表示 ,可能各变量的 live intervals。live interval 保存在以头结点递增的 list 中。Mips 中进 行寄存器分配的普通寄存器是 14 个, ia32 是6个。 第六章 v8之运行时监听 (runtime profile) 理解v8的运行时监听,首先得理解isolate 类。在v8中isolate 是 隔离的框架。在处理web 或者脚本是js引擎通常都是多线程的。一 个isolate 对应一个线程。每个isolate 独立的模块只有在一个线程中 运行。不同的isolate 中的对象是不可以相互调用的。Isolate 其实就是 一个 v8引擎的实例。 LOG 是v8中各种事件的记录,v8在执行各项操作都会在执行之 前先行记录LOG 信息,可以通过--log_all 查看使用事件的处理。也 可以通过分析log 看到很多程序行为,比如lazyCompile,cache miss, bailout等等。v8共定义了43个log事件,而这些事件将会在CpuProfiler 的CodeCreateEvent 方法中使用到。 在v8中定义了宏PROFILE(isolate, Call),每个需要cpu 监听的事 件(属于43个log 事件范围之内)。都可以通过调用该宏进行处理。 比如在函数编译完成后得到SharedFunctionInfo(存有源码script,编 译后的本地代码 ,收集的信息等 ),通过调用该宏可以实现 cpu 监听 。 第七章 v8之LazyCompiler 在v8引擎中,lazyCompiler 是非常常见的。这和v8的执行方式 密切相关 ,v8编译的对象不是以整个文件的方式 ,而是基于函数的 (函 数的存在形式可以是string,script,eval 等等)。在编译一个函数过程 中如果遇到调用其他函数,v8并不在调用函数中编译被调函数,而 是认为被调函数已经编译好了 (这里须得注意就 是v8 通过inline cache 等方式可能将该调用点指向某个地址,在执行到此处是判断是否正 确,若不,发生cache miss。调用lazyCompiler),或者标明该处的被 调函数为lazyCompiler,只有运行到此处时方式运行时中断,调用 runtime profile 中的 lazyCompiler 来编译被调函数,然后跳转执行。 lazyCompiler 的编译方式和全代码生成方式是相同的,只是其使 得v8在只有使用到某具体的地方的时候才进行编译,减少一些不必 要的开销。因为在多函数调用的js程序中,许多分支可能并不时常 到达。这样使得v8只编译有用代码,提高效率。另外通过分析我觉 得lazyCompiler 的另一个好处就是能提高函数的inline。在运行时发 生中断进行编译,这样会收集到更多的信息,而v8认为inline 是一 切优化之母。在运行时lazyCompiler 能进行on-stack replacement。该 技术能在不打断程序继续进行的同时进行代码替换。 可以通过 --trace_lazy 查看哪里地方使用的是 lazyCompile。 第八章 v8之inline cache 8.1 前提条件及实现方式 调用inline cache 是为避免在调用方法和属性存取时的哈希表搜 索,它可以立即缓存之前的搜索结果 。在下次调用同名方法和同名属 性存取时可以直接访问 。在以往的处理动态类型语言的引擎中 ,为提 高属性的访问速度,大多采用哈希表来存储属性。Javascript 是动态 数据类型的,变量无类型,只有在第一次处理时才能确定它的类型 。 故在javascript 中每次存取属性或者方法调用都要检查对象的类型, 每个对象都要有个哈希表。 inline cache 的前提条件就是都假设对象有类型之分,即对象属于 某一个类。然而javascript 无类的概念,使得js 引擎不能直接套用inline cache 技术。为解决这一问题v8引入了隐藏类(hidden classes)的概念。 v8在执行时就分析程序操作,并利用隐藏类为对象提供暂时的类。 隐藏类有两个方面的作用:一个就是将属性名称相同的对象归类 ;另 一个就是识别属性名称不同的对象。 v8 对象 隐藏类 v8中对象属性存储方式 8.2 stubs 函数 v8中有许多的IC stubs 函数,这些函数在每次属性访问的时候都 可能会被用到 ,用来进行访问时判断 ,判断是否是第一次访问该属性 , 然后查找 ,找到之后又要对其他地方相同访问处进行缓存 。另外就是 如果发生cache miss 须得调用相应的函数(比如调用方法miss,则会 编译该被掉函数。如果是属性访问miss,则会访问其prototype 链, 看该属性是否在其原型对象中。 v8不仅实现了对象属性访问,调用方法的inline cache。还实现了 算术运算方面的缓存技术(比如ICU(UnaryOp_Patch) , ICU(BinaryOP_Patch)) 。比较操作方面的缓存技术(比如 ICU(CompareIC_MISS))。 在所以的IC stubs 函数中updateCache 是IC的主要处理程序。每 次IC的引入,miss 后的修改都需要updateCache。而updateCache 与 隐藏类指针 属性 数组 属性哈 希表 IC 的状态密切相关,在v8 中IC 的有5 个状态,分别是uninitialized(IC 初始时的状态),premonomorphic(已经被处理过了,但是单态IC被延 迟的情况),monomorphic(在调用点或者属性访问处只有一种接受类 型),monomorphic_prototype_failure,megamorphic(有多种接受类型 )。 可以通过--trace_ic 查看在哪里发生了ic miss,及其进行的状态转 换。 --watch_ic_patching 来查看 IC绑定的地址,及其稳定性。 第九章 v8之性能分析 (各流程所占时间比 ) 通过对上面的理解可以清楚的知道 v8的轮廓 ,及其实现的具体细 节。对于js引擎来说最为困难和耗时的就是属性访问,方法调用。 而v8在这些方面做足了文章。首先通过全局模板的初始化,全局环 境的配置引入了隐藏类 ,IC,建立了栈帧结构 (并且在这些过程中很 多代码是通过js实现的)。然后就是全代码编译,并在编译函数的过 程中引入了lazy compile 的方式,建立了运行时监听的机制。随后又 在IC信息收集,及运行时监听中得到的信息帮助优化代码的实现 (crankshaft)。并在优化代码的过程中分析代码的行为,插入去优化 点防止过度优化带来的性能下降。 在执行一个 js程序的整个过程中 ,时间上的消耗可以分为几个阶 段:全局初始化时间 +编译时间 +执行时间。 全局初始化时间包括:全局环境配置时间+初始化js程序编译执 行时间 编译时间包括:全代码编译时间+lazyCompile 时间+crankshaft 时 间+IC_miss 的处理时间 (IC处理时间 +可能的 lazyReCompile 时间 )。 全代码编译时间包括:建立 AST 时间 +全代码生成时间 crankshaft 时间包括:建立AST 时间+一次全代码生成时间+建图 时间 +优化时间 +代码安装时间。 通过测试可以发现:全局初始化时间比较固定,在 40ms 到60ms 左右,大部分是60ms。约占每个sunspider 程序运行时间的23.07%。 但是得知道全局初始化时间只会使用一次,比如测试 v8 benchmark, 网页测试sunsipider,全局初始化时间相对于整个benchmark 测试来 说就比较短了 ,占0.13%。因此对于小程序单个测试初始化时间所占 比例就比较大了(因为初始化过程中所完成的工作较多,且本身就是 很多的 js程序,走的是 v8编译用户程序一样的到路 )。 在v8 中所有语法分析建立语法树的过程中,可以通过--trace_parse 查看其花销。3d-cube.js 中约占3.375%,3d-morph.js 中约占3.07%。 在大部分的 sunsupider 中编译语法树约占 3%-5%的时间。 全代码生成过程中:不管是built_in 函数还是user_define 函数都 会掉用平台无关的函数src/codegen.cc 中 FullCodeGenerator::MakeCode 方法来实现全代码生成,因此在此设点 输出全代码的时间进行分析。全代码生成的时间非常短,几乎为0, 极少量的函数需要 20ms。 在crankshaf 中可以通过--trace_opt 查看建图,优化,安装代码每 个过程的时间分布。在 sunspider 中约占中时间 2%-10% 从上的分析可以看出在 v8整个编译执行过程中 ,编译所需时间 在 5%-15%之间。耗时较多的是优化代码生成。
还剩20页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

zootroper

贡献于2017-02-06

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf