北京理工大学 20981 陈罡
一、写在前面的话
随着google io大会上对android 2.2系统展示,一个经过高度优化的android系统(从dalvik虚拟机,到浏览器)呈现在大家面前。开发者们会非常自然地将目光落在dalvik虚拟机方面的改进(包括ndk工具对jni联机单步调试的支持),很多应用接口的调整以及以此为基础的新的应用程序(偶是属于那种喜新不厌旧,找抽性质的人)。对于android 2.2在浏览器方面的优化和改进,在google io大会上只提到了已经全面支持v8 javascript引擎,这种引擎会将浏览器的运行速度提升2-3倍(尽管firefox已经官方发表声明说他们在未来的firefox中会使用一个叫做tracemonkey的javascript引擎,它要比v8更快,但目前来看v8引擎是所有现存javascript引擎中最快的)。
hoho,好东西嘛,自然少不了偶了,下面偶就把自己对v8引擎的一些使用方面的心得体会简单地写一下,希望能够对游戏开发者或者应用程序引擎开发者有一些用处。(稍微表达一下对google的意见,虽然android 2.2已经正式发布了,但source code还没有发布出来,偶等得花儿都谢了。)
二、v8引擎特性简介
v8引擎的最根本的特性就是运行效率非常高,这得益于v8与众不同的设计。
从技术角度来看,v8的设计主要有三个比较特别的地方:
(1)快速对象属性存取机制
javascript这语言很邪门,很不规范,但是动态特性很高,甚至可以在运行时增加或减少对象的属性,传统的javascript引擎对于对象属性存取机制的实现方法是——为运行中的对象建立一个属性字典,然后每次在脚本中存取对象属性的时候,就去查这个字典,查到了就直接存取,查不到就新建一个属性。
如此设计虽然很方便,但是很多时间都浪费到这个“查字典”的工作上了。而v8则采取另外一种方式——hidden class(隐藏类?!偶怕翻译得不贴切因此直接把原文写上来了)链的方式,在脚本中每次为对象添加一个新的属性的时候,就以上一个hidden class为父类,创建一个具有新属性的hidden class的子类,如此往复递归进行,而且上述操作只在该对象第一次创建的时候进行一次,以后再遇到相同对象的时候,直接把最终版本的hidden class子类拿来用就是了,不用维护一个属性字典,也不用重复创建。
这样的设计体现了google里面天才工程师们的才华(当然第一次运行的时候肯定要慢一些,所以google那边强调,v8引擎在多重循环,以及重复操作一些对象的时候速度改善尤为明显,大概这种设计也是其中的一个原因吧,当然最主要的原因还在动态机器码生成机制)
(2)动态机器码生成机制
这一点可以类比一下java虚拟机里面的jit(just in time)机制,地球人都知道,java的运行效率很低,尤其在使用多重循环(也就是,for循环里面还有个for循环里面还有for循环*^&@*#^$。。。就当此注释是废话好了)的时候,sun为了解决这个问题,在jvm虚拟机里面加入了jit机制,就是在.class运行的时候,把特别耗时的多重循环编译成机器码(也就是跟exe或elf中保存的代码一样的可执行二进制代码),然后当下次再运行的时候,就直接用这些二进制代码跑,如此以来,自然运行效率就提高了。android 2.2在dalvik里面也已经加入了jit技术,所以会有如此大的性能提升,但是对于一个javascript引擎中引入此技术来提高脚本的运行效率,偶还是第一次看到(或许是偶孤陋寡闻了,欢迎对此有研究的朋友不吝斧正)。
这种设计在本文的下半部分,研究如何在c++程序中嵌入v8引擎、执行javascript脚本的时候,会有更加深入的理解,因为每次运行脚本之前,首先要调用compile的函数,需要对脚本进行编译,然后才能够运行,由此可以看到动态代码生成机制的影响深远。
这种设计的好处在于可以极大限度地加速javascript脚本运行,但是自然也有一些问题,那就是移植的问题,目前从v8的代码上来看,v8已经支持ia32(也就是x86了),arm,x64(64位的,偶现在还没那么幸运能用上64位的机器),mips(是apple们用的),其他的javascript引擎,只需要把代码重新编译一下,理论上就能够在其他不同的硬件平台上跑了,但是从这个动态机器码生成的机制来看,虽然v8很好,很强大,但是把它弄到其他的平台上似乎工作量不小。
(3)高效的垃圾回收机制
垃圾回收,从原理上来说就是对象的引用计数,当一个对象不再被脚本中其他的对象使用了,就可以由垃圾回收器(garbage collector)将其释放到系统的堆当中,以便于下一次继续使用。
v8采用的是stop-the-world(让世界停止?!其实真正的意思就是在v8进行垃圾回收的时候,中断整个脚本的执行,回收完成后再继续执行脚本,如此以来,可以集中全部cpu之力在较短的时间内完成垃圾回收任务,在正常运行过程中坚决不回收垃圾,让全部cpu都用来运行脚本)垃圾回收机制。从偶的英文水平来看,其他的描述,诸如:快速、正确、下一代之类的都是浮云,stop-the-world才是根本。
以上是偶对v8设计要点和特性方面的简单研究,英语好的朋友可以无视偶在上面的聒噪,直接看v8的design elements原文,原文的地址如下:
三、下载和编译v8的方法
ok,既然v8引擎这么好,那么现在就开始动手,搞一个出来玩玩。与以往一样,偶的开发环境是slackware13.1。
关于v8引擎的下载和编译方法,英文好的朋友可以直接看google code上面的介绍,具体的链接地址如下:
偶在此只是简单地把要点提一下,顺便聊聊注意事项:
(1)v8可以在winxp, vista, mac os, linux(arm和intel的cpu都行)环境下编译。
(2)基本的系统要求:
a、svn版本要大于等于1.4
b、win xp要打sp2补丁(现在最新的补丁应该是sp3了)
c、python版本要大于等于2.4
d、scons版本要大于等于1.0.0(google这帮家伙们还真能折腾,用gmake就那么费劲吗?非要弄个怪异的编译工具,这个scons是基于python的自动化编译工具,功能上跟linux下面的Makefile非常类似,不一样的是Makefile的脚本是gmake的语法,而scons的配置脚本的语法则是python,看来v8引擎的开发者们是python的铁杆粉丝,这个scons的安装方法偶就不再聒噪了,python install setup.sh,相信熟悉python的朋友一定非常清楚了。)
e、gcc编译器的版本要大于4.x.x
(3)v8的下载地址:
svn checkout v8-read-only
(4)基本的编译方法:
a、查看v8配置脚本中参数的方法:scons --help
b、查看scons命令本身提供参数的方法:scons -H (这里的“H”一定要大写)
c、设置环境变量:
export GCC_VERSION=44(这个一定要设置,否则会导致一大堆错误,天知道google guys们是如何编写scons的配置脚本的,个人感觉他们写这个编译脚本的时候应该是用mac book,在leopard系统上玩的,而偶还在用价廉物美的lenovo,使用slackware。。。)
d、开始编译,编译的命令很简单:scons mode=release library=shared snapshot=on
e、经过漫长的编译过程,会看到一个叫做libv8.so的库(当然用library=static可以编译出libv8.a的静态库),把这个so库手工拷贝到/usr/local/lib,然后,ldconfig一下就好了,然乎把v8-read-only/include目录下的几个.h文件拷贝到/usr/local/include目录下。到此为止,v8引擎已经顺利地安装到了机器上。
f、经过e以后,我们可以简单地测试一下是否能够工作。还需要编译一个可执行程序出来,例如——shell程序。编译的方法非常简单:scons sample=shell,然后就是等待即可。
好了,经过上面的过程,大家应该能够很顺利地生成libv8.so这个库了,下一步偶开始研究如何在自己的c++代码中调用这个库了。
四、v8引擎的调用方法
1、基本概念
在使用v8引擎之前,必须知道三个基本概念:句柄(handle),作用域(scope),上下文环境(context,大爷的老外的这个context就是绕口,没法翻译成中文,可以简单地理解为运行环境也可以)
(1)句柄(Handle)
从实质上来说,每一个句柄就是一个指向v8对象的指针,所有的v8对象必须使用句柄来操作。这是先决条件,如果一个v8对象没有任何句柄与之相关联,那么这个对象很快就会被垃圾回收器给干掉(句柄跟对象的引用计数有很大关系)。
(2)作用域(Scope)
从概念上理解,作用域可以看成是一个句柄的容器,在一个作用域里面可以有很多很多个句柄(也就是说,一个scope里面可以包含很多很多个v8引擎相关的对象),句柄指向的对象是可以一个一个单独地释放的,但是很多时候(尤其是写一些“有用”的程序的时候),一个一个地释放句柄过于繁琐,取而代之的是,可以释放一个scope,那么包含在这个scope中的所有handle就都会被统一释放掉了。
(3)上下文环境(Context)
从概念上讲,这个上下文环境(以前看一些中文的技术资料总出现这个词,天知道当初作者们是如何想的,不过这事情就是约定俗成,大家都这么叫也就习惯了)也可以理解为运行环境。这就好比是linux的环境变量,在执行javascript脚本的时候,总要有一些环境变量或者全局函数(这些就不用偶解释了吧?!就是那些直接拿过来就用,根本不需要关心这些变量或者函数在什么地方定义的)。偶们如果要在自己的c++代码中嵌入v8引擎,自然希望提供一些c++编写的函数或者模块,让其他用户从脚本中直接调用,这样才会体现出javascript的强大。从概念上来讲,java开发中,有些功能jvm不提供,大家可以用c/c++编写jni模块,通过java调用c/c++模块来实现那些功能。而类比到javascript引擎,偶们可以用c++编写全局函数,让其他人通过javascript进行调用,这样,就无形中扩展了javascript的功能。java+jni的开发模式与javascript+c++module是一样的思路,只是java更加复杂,系统库更加丰富;而javascript相对java来说比较简单,系统库比较少。仅此而已。
2、开始在c++代码中嵌入v8引擎
(1)基本的编译方法
基本的编译方法很简单,只要上面安装v8引擎的过程中没有什么问题,就可以直接把v8引擎作为一个普通的动态链接库来使用,例如:在编译的时候加入-I/usr/local/include,在链接的时候加入-L/usr/local/lib -lv8就足够了。这里需要提一句,由于v8引擎是完全使用c++编写的(hoho,最近linus在blog上跟人吵架,声称c++是垃圾程序员使用的垃圾语言,闹得沸沸扬扬。偶也十分喜欢c语言,但是在此不对linus的言论做任何评论,好东西嘛能用、会用就是了。)
例如:
g++ -c test.cpp -I/usr/local/include
g++ -o test test.o -L/usr/local/lib -lv8
(2)在使用v8引擎中定义的变量和函数之前,一定不要忘记导入v8的名字空间
using namespace v8;
(3)在c++程序中简单地执行v8脚本引擎的方法如下:
// 创建scope对象,该对象销毁后,下面的所有handle就都销毁了
HandleScope handle_scope ;
// 创建ObjectTemplate对象,这个对象可以用来注册c++的全局函数供给javascript调用
// 在此演示中先可以忽略
Handle global_templ = ObjectTemplate::New() ;
// 创建运行环境
Handle exec_context ;
// 创建javascript脚本的存储对象,该对象存放从文件中读取的脚本字符串
Handle js_source ;
// 创建用于存放编译后的脚本代码的对想
Handle
阅读(1078) | 评论(0) | 转发(0) |