脚本引擎小PK: SpiderMonkey vs V8(二)
应用脚本引擎
要运行前文中的两段脚本代码,必须实现全局函数myprint和类MyClass,其中,MyClass有push()和pup()方法。
首先,我们来看看各自的框架代码
SpiderMonkey版
1. #define XP_WIN
2. extern "C"{
3. #include "jsapi.h"
4. }
5. // 由argv[1]指定脚本文件
6. int main(int argc, char* argv[])
7. {
8. // 需要一个runtime和一个以上的上下文
9. JSRuntime *rt = JS_NewRuntime(0x 400000L );// 参数指定最大BYTE数
10. JSContext *cx = JS_NewContext(rt, 8192); //8192是栈大小
11.
12. // 设置JS上下文参数
13. JS_SetOptions(cx, JSOPTION_VAROBJFIX);// 尽量兼容ECMAScript
14. JS_SetVersion(cx, JSVERSION_LATEST); // 支持最新版本JS
15.
16. // 定义全局变量
17. JSObject *globalObj = JS_NewObject(cx, NULL, 0, 0); //生成全局对象
18. JS_InitStandardClasses(cx, globalObj); //向全局对象注册标准方法和属性
19.
20. // 编译JS文件,得到编译后的脚本对象
21. JSScript * jsp = JS_CompileFile(cx, globalObj, argv[1]);
22. if(jsp == NULL){
23. std::cout << "编译js文件出错!" << std::endl;
24. return -1;
25. }
26.
27. // 执行脚本对象
28. jsval rval;
29. JS_ExecuteScript(cx, globalObj, jsp, &rval);
30.
31. // 销毁脚本对象
32. JS_DestroyScript(cx, jsp);
33.
34. // 关闭JS环境
35. JS_DestroyContext(cx);
36. JS_DestroyRuntime(rt);
37. JS_ShutDown();
38. return 0;
39. }
V8版
1. //这里用的是DLL版本,如果是静态版本就不用下面这句了
2. #include <iostream>
3. #include <string>
4. #include <fstream>
5. #define USING_V8_SHARED 1
6. #include "v8.h"
7. using namespace v8;
8. // 由argv[1]指定脚本文件
9. int main(int argc, char* argv[])
10. {
11. // V8不支持直接读文件,只好自己把脚本文件内容讲到字符串里去了-_-
12. std::ifstream ifstm(argv[1]);
13. if(!ifstm) return -1;
14. std::string s( std::istreambuf_iterator<char>(ifstm.rdbuf()),
15. std::istreambuf_iterator<char>());
16.
17. HandleScope handle_scope; // 管理Handle,在它生命周期内的Handle都归它管
18. Handle<ObjectTemplate> global = ObjectTemplate::New(); //准备一个全局对象模板
19.
20. Persistent<Context> context = Context::New(NULL,global); //建立上下文
21. Context::Scope context_scope(context);//管理上下文
22.
23. Handle<String> source_obj = String::New(s.c_str(), s.length()); //源码字符串
24. Handle<Script> script = Script::Compile(source_obj);//编译
25. Handle<Value> result = script->Run();//运行
26.
27. context.Dispose();
28. return 0;
29. }
上面的这些代码已经可以解析标准JS脚本了,你可以把我们试验用的《脚本一》里最后一句myprint注释掉,然后用上面的程序来运行。
实现全局函数myprint
myprint是宿主程序向脚本提供的一个全局函数,用于向控制台输出传给它的所有参数,这样我们就可以直接在脚本里输出信息了。
SpiderMonkey版
1. //-----------------自定义函数---------------------
2. JSBool myprint(JSContext* cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
3. {
4. for(uintN i=0; i<argc; i++)
5. {
6. JSString *str = JS_ValueToString(cx, argv[i]);
7. std::cout << JS_GetStringBytes(str) << ' ' ;
8. }
9. return JS_TRUE;
10. }
11. void addMyFunc(JSContext* cx, JSObject *objParent) //加入自定义函数
12. {
13. JSFunctionSpec my_functions[] = {
14. JS_FS("myprint", myprint, 0, 0, 0),
15. JS_FS_END
16. };
17. JS_DefineFunctions(cx, objParent, my_functions);
18. }
在编译之前加上addMyFunc函数就可以为脚本加入myprint函数了。如:
1. ...
2. // 定义全局变量
3. JSObject *globalObj = JS_NewObject(cx, NULL, 0, 0); //生成全局对象
4. JS_InitStandardClasses(cx, globalObj); //向全局对象注册标准方法和属性
5. //------加入自定义函数------
6. addMyFunc(cx, globalObj);
7. //------------------------
8. // 编译JS
9. JSScript * jsp = JS_CompileFile(cx, globalObj, argv[1]);
10. ...
V8版
1. //-----------------自定义函数---------------------
2. Handle<Value> MyPrint( const Arguments& args) //myprint函数实体,打印所有参数
3. {
4. HandleScope handle_scope; //管理这里的Handle(函数退出时释放循环里的Handle<String> s)
5. for(int i=0; i<args.Length(); i++)
6. {
7. Handle<String> s = args[i]->ToString();
8. std::cout << *(String::AsciiValue(s)) << std::endl;
9. }
10. return Undefined();
11. }
12.
13. void addMyFunc(Handle<ObjectTemplate> global) //加入自定义函数
14. {
15. global->Set(String::New("myprint"), FunctionTemplate::New(MyPrint)); //自定义一个myprint函数
16. }
向全局对象模板注册这个函数就可以在脚本中使用myprint了,如:
1. HandleScope handle_scope; // 管理Handle,在它生命周期内的Handle都归它管
2. Handle<ObjectTemplate> global = ObjectTemplate::New(); //全局对象
3. addMyFunc(global); //为全局对象加上自定义函数
加入类MyClass
这里,打算把MyClass实现成一个队列类,方法有:push、pop;属性有:front、back、empty、size、array。直接使用std::deque作为队列实现的底层支持,下面开始分别用SpiderMonkey和V8的方式对它进行包装。
SpiderMonkey版
1. // 属性Getter函数
2. enum{ EQFRONT, EQBACK, EQSIZE, EQEMPTY, EQARRAY};
3. JSBool MyClassGetter(JSContext *cx, JSObject *obj, jsval id, jsval *vp)
4. {
5. std::deque<int>* pQ = (std::deque<int>*)JS_GetPrivate(cx, obj);
6. if(!JSVAL_IS_INT(id)) return JS_TRUE;
7. switch(JSVAL_TO_INT(id))
8. {
9. case EQFRONT:
10. if(pQ->empty()) return JS_FALSE;
11. *vp = INT_TO_JSVAL( pQ->front() );
12. break;
13. case EQBACK:
14. if(pQ->empty()) return JS_FALSE;
15. *vp = INT_TO_JSVAL( pQ->back() );
16. break;
17. case EQSIZE:
18. *vp = INT_TO_JSVAL( pQ->size() );
19. break;
20. case EQEMPTY:
21. *vp = BOOLEAN_TO_JSVAL( pQ->empty() );
22. break;
23. case EQARRAY: //数组操作
24. {
25. //用JS_NewArrayObject生成数组
26. JSObject *objA = JS_NewArrayObject(cx, 0, NULL);
27. jsint idx = 0;
28. for(std::deque<int>::iterator itr=pQ->begin(); itr!=pQ->end(); ++itr)
29. {
30. jsval v = INT_TO_JSVAL(*itr);
31. //用JS_SetElement加入指定索引的数据
32. JS_SetElement(cx, objA, idx++, &v);
33. }
34. *vp = OBJECT_TO_JSVAL(objA);
35. break;
36. }
37. break;
38. }
39. return JS_TRUE;
40. }
41. // "构造"
42. JSBool MyClassConstructor(JSContext *cx, JSObject *obj, uintN argc,
43. jsval *argv, jsval *rval)
44. {
45. std::deque<int> *pQ = new std::deque<int>();
46. JS_SetPrivate(cx, obj, pQ);
47. return JS_TRUE;
48. }
49. // "析造"
50. void MyClassDestructor(JSContext *cx, JSObject *obj)
51. {
52. std::deque<int>* pQ = (std::deque<int>*)JS_GetPrivate(cx, obj);
53. delete pQ;
54. }
55. // 类方法
56. JSBool QPush(JSContext *cx, JSObject *obj, uintN argc,
57. jsval *argv, jsval *rval)
58. {
59. std::deque<int>* pQ = (std::deque<int>*)JS_GetPrivate(cx, obj);
60. if(JSVAL_IS_INT(argv[0]))
61. {
62. pQ->push_back(JSVAL_TO_INT(argv[0]));
63. }
64. return JS_TRUE;
65. }
66. JSBool QPop(JSContext *cx, JSObject *obj, uintN argc,
67. jsval *argv, jsval *rval)
68. {
69. std::deque<int>* pQ = (std::deque<int>*)JS_GetPrivate(cx, obj);
70. if(!pQ->empty())
71. {
72. pQ->pop_front();
73. }
74. return JS_TRUE;
75. }
76. // 定义类
77. JSClass myClass={
78. "MyClass",
79. JSCLASS_HAS_PRIVATE,
80. JS_PropertyStub, JS_PropertyStub, //add del
81. MyClassGetter, JS_PropertyStub, //get set
82. JS_EnumerateStub,
83. JS_ResolveStub,
84. JS_ConvertStub,
85. MyClassDestructor
86. };
87. JSPropertySpec myClassProp[]={
88. {"front", EQFRONT, JSPROP_READONLY},
89. {"back", EQBACK, JSPROP_READONLY},
90. {"size", EQSIZE, JSPROP_READONLY},
91. {"empty", EQEMPTY, JSPROP_READONLY},
92. {"array", EQARRAY, JSPROP_READONLY},
93. {0}
94. };
95. JSFunctionSpec myClassMethod[]={
96. JS_FS("push", QPush, 1, 0, 0),
97. JS_FS("pop", QPop, 0, 0, 0),
98. JS_FS_END
99. };
100. void addMyClass(JSContext* cx, JSObject *objParent) //加入自定义类
101. {
102. JSObject * newobj = JS_InitClass(cx, objParent, NULL,
103. &myClass, MyClassConstructor, 0,
104. NULL, myClassMethod,
105. NULL, NULL);
106. JS_DefineProperties(cx, newobj, myClassProp);
107. }
V8版
1. // “构造”
2. Handle<Value> MyClassConstructor( const Arguments& args)
3. {
4. Handle<Object> obj = args.This();
5. HandleScope handle_scope;
6. testq *ptr = new testq;
7. obj->SetInternalField(0, External::New(ptr));
8. return obj;
9. }
10. // 类属性Getter函数
11. Handle<Value> GetQFront(Local<String> property,
12. const AccessorInfo &info)
13. {
14. HandleScope handle_scope;
15. Handle<Object> self = info.Holder();
16. std::deque<int>* pQ = (std::deque<int>*) self->GetPointerFromInternalField(0);
17. return Undefined();
18. }
19.
20. Handle<Value> GetQBack(Local<String> property,
21. const AccessorInfo &info)
22. {
23. HandleScope handle_scope;
24. Local<Object> self = info.Holder();
25. std::deque<int>* pQ = (std::deque<int>*) self->GetPointerFromInternalField(0);
26. return Integer::New(pQ->back());
27. }
28. Handle<Value> GetQSize(Local<String> property,
29. const AccessorInfo &info)
30. {
31. HandleScope handle_scope;
32. Local<Object> self = info.Holder();
33. std::deque<int>* pQ = (std::deque<int>*) self->GetPointerFromInternalField(0);
34. return Integer::New(pQ->size());
35. }
36. Handle<Value> GetQEmpty(Local<String> property,
37. const AccessorInfo &info)
38. {
39. HandleScope handle_scope;
40. Local<Object> self = info.Holder();
41. std::deque<int>* pQ = (std::deque<int>*) self->GetPointerFromInternalField(0);
42. return Boolean::New(pQ->empty());
43. }
44. Handle<Value> GetQArray(Local<String> property,
45. const AccessorInfo &info)
46. {
47. HandleScope handle_scope;
48. Local<Object> self = info.Holder();
49. std::deque<int>* pQ = (std::deque<int>*) self->GetPointerFromInternalField(0);
50. Handle<Array> array = Array::New(pQ->size());
51. int i=0;
52. for(std::deque<int>::iterator itr=pQ->begin(); itr!=pQ->end(); ++itr)
53. {
54. array->Set(Integer::New(i++), Integer::New(*itr));
55. }
56. return array;
57. }
58. // 类方法
59. Handle<Value> QPush( const Arguments& args)
60. {
61. HandleScope handle_scope;
62. Local<Object> self = args.Holder();
63. Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
64. std::deque<int>* ptr = (std::deque<int>*)wrap->Value();
65. ptr->push_back( args[0]->Int32Value() );
66. return Undefined();
67. }
68. Handle<Value> QPop( const Arguments& args)
69. {
70. HandleScope handle_scope;
71. Local<Object> self = args.Holder();
72. Local<External> wrap = Local<External>::Cast(self->GetInternalField(0));
73. std::deque<int>* ptr = (std::deque<int>*)wrap->Value();
74. ptr->pop_front();
75. return Undefined();
76. }
77. //加入自定义类
78. void addMyClass(Handle<ObjectTemplate> global)
79. {
80. Handle<FunctionTemplate> myc = FunctionTemplate::New(MyClassConstructor);
81.
82. HandleScope handle_scope;
83. myc->SetClassName(String::New("MyClass"));
84. global->Set(String::New("MyClass"), myc);
85.
86. Handle<ObjectTemplate> proto = myc->PrototypeTemplate();
87. proto->Set("push", FunctionTemplate::New(QPush));
88. proto->Set("pop", FunctionTemplate::New(QPop));
89.
90. Handle<ObjectTemplate> inst = myc->InstanceTemplate();
91. inst->SetInternalFieldCount(1);
92. inst->SetAccessor(String::New("front"), GetQFront);
93. inst->SetAccessor(String::New("back"), GetQBack);
94. inst->SetAccessor(String::New("size"), GetQSize);
95. inst->SetAccessor(String::New("empty"), GetQEmpty);
96. inst->SetAccessor(String::New("array"), GetQArray);
97. }
同样,把这个addMyClass函数和之前的addMyFunc放到一起就可以为脚本加入一个MyClass类了。
速度测试
脚本所需的类和方法已经装备完毕,接下来就可以做我们的速度测试了。
下面的测试速度是在我的电脑上,Core2 T7100, 1G 内存上10次测试取平均的结果,编译环境为VC2005 Express,Release方式。单位为秒。
从图上可以直观地看出,V8的速度要比SpiderMonkey快,尤其是脚本一,V8的速度是SpiderMonkey的10倍还多。
总结
就执行速度而言,V8具有压倒性的优势,这和Google的官方宣传是一致的。
大 小方面,在VC的Release下,V8的动态编译库大小为 1.39M ;SpiderMonkey的动态编译库大小为708k,SpiderMonkey 胜出。静态编译?嗯,还是算了吧,偶编译的静态V8.lib足足有 100M ,和程序链接时要花费10秒钟的时间,调试程序太郁闷了~~。
对于编程简易程度来说,两方各有优势,这个因人而异,我觉得SpiderMonkey的概念更简洁一点;V8的Template、Scope、Handle等东东还是需要一点时间去理解的。而且还有一点,SpiderMonkey的官方文档比V8的丰富。
如 果各位细心的话,可以发现V8版的addMyClass没有“析构”函数,也就是说在脚本里建立出来的MyClass是存在内存泄漏的!我花了很长时间, 查了N多资料也没找到如何在V8的GC工作时回收InternalField里的数据(经试验,Persistent的MakeWeak方法无效)。目前 的解决方法只有为类提供一个destory函数,在脚本中显式调用来回收,不过这样就享受不了垃圾回收了。如果各位有更好的方法,请留言提出哦,非常感 谢!
最后我们还得提到IE的脚本引擎,调用IE脚本引擎的优点是操作系统集成,所以不用附带一个DLL或链接一大块脚本引擎,这样发布的软件会小一点点,而且ActiveX Scripting技术(请参考本站相关文章)对于熟悉COM编程的人来说编写代码也很简单。但是~~缺点也很明显,速度不是慢,而是相当慢~~本来偶还想用它也运行一下《脚本一》的代码作为参考的,结果等了一分种无响应后郁闷地强制退出~~