js引擎中的懒解析
来源:互联网 发布:mac本的压缩文件是什么 编辑:程序博客网 时间:2024/06/05 15:22
本文为翻译,原文:http://ariya.ofilabs.com/2012/07/lazy-parsing-in-javascript-engines.html
现代浏览器都可以把一个function的函数体解析延迟到真正需要的时候,那这又是如何工作的呢?
IE的团队最近发表了一篇关于使用懒解析来提高性能的文章“Advances in JavaScript Performance in IE10 and Windows 8 “。实际上稳定版的IE9已经开始使用这种方式了,只不过IE10更进一步优化了性能。据IE团队称(IE中使用的js引擎是Chakra ):
为进一步减少首次执行时间,只有当functions要执行的时候Chakra才处理和释放字节码,这种机制称之为延迟解析。我们来看一个简单的例子,看下它是如何工作的,假设有一段js代码:
function add(x, y) { return x + y; }function mul(x, y) { return x * y; }alert(add(40, 2));
在js引擎可以执行代码之前,必须先把代码传给解析器。解析器的目的就是做语法分析,并得到一个抽象的语法树(AST).可以使用parser demo 这个demo工具来了解下语法树的大致生成结构。完整的语法书是非常复杂的,用通俗点的语言表达就是:
定义一个function,叫add,它接受x,y参数,它有一段返回申明,返回值是x,y的二进制操作+定义一个function,mul,接受x,y参数,它有一段返回申明,返回x,y的二进制操作*创建一个alert函数的调用,它的参数是add函数接受40,2参数的结果。
基于上面这个语法书,一些奇怪的事情发生了。最后,解析器质性你的代码的时候弹了个消息框。但是你会注意到,上面这个解析过程有一个浪费的步骤,浪费精力去解析mul函数,而实际上mul并没有被调用。这个例子看起来很简单,实际上(根据微软的 JSMeter research),大部分的已声明的函数却没有被调用到。
现代浏览器都开始逐渐使用lazy paring来替代这种单次完整的解析。上面的解析工作就变成这样了:
定义一个add函数,函数体是“{ return x + y; }”定义一个mul函数,函数体是“{ return x * y; }”调用alert函数,传入add函数处理40,2参数的结果作为参数这样,解析器就不会深入到每个函数体内部,在执行过程中,过程将继续:
调用add函数,Hmm,但是它还没有解析,调用实时解析“{ return x + y; }”它接受x,y参数,返回x,y的+操作结果
函数源代码的解析就被推迟了,只有当需要执行之前才解析。懒解析同样需要解析传入的代码,因为它需要定位出整个函数的body,比如你看到了”function add(x, y) {“ ,然后你需要找到函数体的结尾”}“。这不能通过正则和任何形式的扫描来完成,解析器如果是时解析的话需要处理整份代码。幸运的是,解析不需要去找结束标记了,这样就可以达到最优化。我们根本不需要语法树,因为它不会被任何人访问。另外,代码路径也不需要从内存中划分堆出来存储。分配内存消耗了资源也拖慢了速度。
...(原文中的一个生活比喻偶就不翻译了,丫的,又累又啰嗦啊,懂了就行了。如果真的是还没理解,那只能怪我翻译水平有限了,我再翻译也是徒劳了,还是问问元芳吧)....
我们假设来解析一个while语句块来作为比较:
‘while’ ‘(‘ Expression ‘)’ Statement
真正的解析需要明白并产生一个抽象的语法树(AST)代表它的结构,在js中看来像这样:
function realParseWhileStatement(){ expect('while'); expect('('); var expression = parseExpression(); expect(')'); var statement = parseStatement(); // node for the AST return { type: 'WhileStatement', test: expression, body: statement };}
如果使用lazy parsing,我们并不关心结果,所以代码就更简单:
function lazyParseWhileStatement(){ expect('while'); expect('('); parseExpression(); expect(')'); parseStatement();}
显然,还有很多其它函数语来解析各种语法规则。底线就是解析器要解析所有标记,直到函数体完成。这样一来,它就知道函数的结尾标记,匹配函数的开始标记。
在赖解析中如果我们遇到一个内嵌函数呢?这个规则同样适用,内嵌函数同样会被懒解析。是不是有点想函数盗梦空间?
实际上,lazy 解析器会更复杂一些,需要妥善处理严格模式,要考虑解析错误、堆栈溢出等其他情况。
我们来看下lazy parsing在2大流行的js引擎中的实现。
webkit和safari 中使用的JavaScriptCore (JSC),JavaScriptCore 代码定义在Source/JavaScriptCore
目录,实现lazy parsers的相关代码:
parser/Parser.hparser/Parser.cppparser/SyntaxChecker.h
正常解析和懒解析的实现JavaScriptCore都在同一个代码里,通过专门的C++模板实现。解析器本身不构建语法树,这份工作交给TreeBuilder。这里有2中builder,ASTBuilder
and SyntaxChecker,
后一种本质上讲不做什么工作,除非被解析器调用,解析器可以在向前构造的时候在任何点停下来。SyntaxChecker扮演的是一个语法检查的角色。
JSC解析器会调用语法检查器,当在需要解析一个function的body的时候,可以看parseFunctionBody,在parseFunctionInfo中被调用。function的body的语法检查完之后,包含函数体范围的大括号就会被保存起来,当函数调用,真正解析的时候有用。因为JSC保存整个源,保存的范围值足够大,所以没有必要复制源字符串。
Chrome,Node.js中的V8引擎也类似,懒解析的相关代码在:
src/preparser.ccsrc/preparser.hsrc/preparser-api.cc
跟JSC不同的是,v8对普通解析和懒解析有2种不同的代码(但是有类似的接口)。V8里称之为 PreParser ,preparser在v8遇到一个函数体时触发,像Parser::ParseFunctionLiteral。有趣的是,V8为一个特殊的情况做了优化。即时函数调用的情况,为了不污染全局环境,流行的写法,比如:
var foobar = (function() { // do something // return the module object})();
这种方式非常普遍,v8做了一个启发式检测,如果遇在一个function前遇到(,然后就不触发lazy parsing,使用真正普通的解析,这样v8就会为函数体产生语法树。
那firefox中使用的SpiderMonkey又会怎样呢,还没有lazy parsing,但是正在计划实现。可以看bug 678037,这一举措可能将进一步提升firefox的性能。
- js引擎中的懒解析
- jdk6 中的js引擎
- js 创建Ajax引擎 "<!--" 解析
- 解析JS的脚本解析引擎
- 解析js中的call
- js中的this解析
- js中的Prototype解析
- IE 中的 VBScript 和 JScript 解析引擎
- MySQL中的存储引擎的解析
- Chipmunk引擎在Cocos2d-js中的使用
- JS中的构造函数解析
- JS中的构造函数解析
- JS中的arguments对象解析
- js中的eval解析json
- JS解析URL中的参数
- js中的arguments的解析
- js中的prototype的解析
- 高性能JavaScript模板引擎template.js原理解析
- C++模板函数
- 早起
- ACE安装、配置与测试
- 使用CMake构建Ogre工程文件时的一些小细节
- Myeclipse中的jdbc.properties的中文注释为乱码问题
- js引擎中的懒解析
- Centos安装KDE或GNOME
- Mysql Oracle Java 数据类型对照
- C++指针解惑
- Response1
- IAR软件应用中的错误提示
- android BroadcastReciver 笔记
- T-state
- 【容斥原理、gcd】初步