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


发布评论