分享

V8 JS引擎

 wusiqi111 2019-05-06

https://blog.csdn.net/allen8612433/article/details/80329022

一.  Google开发V8

Google (丹麦)研发小组在 2006 年开始研发 V8 ,部分的原因是 Google 对既有 JavaScript 引擎的执行速度不满意, 

2008年推出chrome, 巨大的速度优势迅速占领市场2017chrome的市场占有达到59%. 

二.  常见JS引擎

JScript(IE6,IE7, IE8)

Chakra(IE9,IE10, IE11, IE Edge)

SpiderMonkey(Firefox)

JavaScriptCore(Safari)

V8(Chrome)

这些是业界常见的JS引擎

应用在IEJscript

Chakra应用在新版的IE

FireFoxSpiderMonkey

SafariJavascriptCore

chromeV8

V8的速度相比最慢的IE快多少呢?

2008年的刚发布chrome有数据表明快56.

三.  JS语言与native语言相比有哪些优势.

1.脚本语言是一行行代码直接在解释器中解释执行的.

而非脚本语言是要编译成二进制机器码执行的.

对于具有很多模块的大工程,

如果没有修改模块对接的接口则编译单独模块即可,

如果修改了模块对接的接口那么仅编译单独模块就会引起运行时错误.

所以修改了模块对接接口往往需要把相关模块全部重新编译.

以我们的客户端native模块打包为例服务器多线程编译仍然需要数十分钟.

chrome为例的所有模块编译一遍需要半天时间.

脚本语言少了编译的过程修改之后可以立即看到修改的结果.

2.弱类型,native语言的数据类型很多C++为例.

C++有十几种类型,float, double, int,short, bool,char, wchar,string, 指针,

每一种类型有自己的特性如果不按照特性操作,

就会出现数据错误严重的导致异常进程崩溃系统崩溃等.

所以C++中对于函数传递参数有比较严格的要求,

需要清晰的定义数据结构函数参数的类型和数量.

好处是运行效率高节省内存和磁盘.

这对于编写和维护代码的人而言就有非常大的工作量,

所以开发效率也就低于脚本语言的开发.

3.安全这里所说的安全性是针对用户而言的,

JS一般运行在浏览器内访问能力就是承载它的JS引擎给予的.

所以一般情况下不会导致用户的进程崩溃更不会引起系统崩溃.

这也是为什么要使用webnative替换Vmpage的一个重要原因.

4.跨平台

JS引擎在哪里, JS就可以运行在哪里,

据北京框架组的徐云飞透露,

他们试着在mac客户端集成了JsEngine.dll

然乎把我们开发的JS应用上去也可以运行.

四. JS的性能问题

JavaScript 除了少数类型,

大部分是动态的对象允许在任何时间,

在对象上新增或是删除属性和方法,

所以属性和方法需要通过键值查表的方式查找访问.


五. native的性能优势

native语言往往变量是结构化的,

对象要么是类对象要么是单一类型的.

在编译期变量就知道了结构有多大属性有多少,

按照类型的大小和个数可以计算出内存块的偏移值,

根据偏移值就可以获取到属性,子成员子函数等.


六.  JS的性能优化

V8的性能优化主要包含四个点:

JIT( just in time): 
意思是即时编译,

解释器直接产生可执行数据,不产生中间码.

垃圾回收:

精确回收相比保守的回收涉及减少碎片内存复用要经常移动内存块所以操作要复杂得多.

自动侦测需要释放的内存因为侦测有消耗所以分为新生代和老年代来区分不同的侦测频率.

内嵌缓存:

缓存查找属性的结果减少一次属性查找

对于短的函数直接以函数内的实现替换调用减少一次函数调用

隐藏类:

让相似的JS对象以native的数据结构的形态存在于内存之中.

从而加速对象的存取操作不过一旦数据需要出现结构性改变,

此时又会将数据转换成另一个隐藏类除非隐藏类无法支持下去.

七. V8 编译改进

这里详细解释一下编译过程

这里为了解释说明了三种过程分别是老的JS引擎

Java的混合模式, V8引擎的编译

首先代码是一个文本第一步需要经过语言分析器解析生成抽象语法树.

这个步骤三者都是需要的.

对于老的JS引擎生成的是字节码通过字节码编译器来运行.

字节码的执行效率要低于直接在CPU上运行的机器码.

于是Java做出了改进将字节码编译成机器码执行,

并且让这两种方式并存.

V8则是绕过了其中的字节码步骤直接生成机器码.

这样它的运行效率可以和native语言媲美.

然而由于这样拉长了编译时间拉长并且生成的机器码占用了更多的内存.


八. 抽象语法树

抽象语法树是通过对一段JS代码文本进行语法解析生成的对象和对象之间的操作关系.

九. V8编译机器码

十. V8 Ignition

JIT编译带来了内存占用的问题和编译器加长的问题.

于是在20174月改进了编译方法, Ignition新架构是支持混合模式的

混合的方式是将第一层调用编译成机器码第一层以上的调用编译成字节码.

十一. V8的隐藏类

让相似的JS对象以native的数据结构的形态存在于内存之中.

从而加速对象的存取操作不过一旦数据需要出现结构性改变,

此时又会将数据转换成另一个隐藏类除非隐藏类无法支持下去.

如图所示有一个对象如果它没有x, y, 那么它在的隐藏类是C0

当它增加了x, 隐藏类转变成C1, 通过C1可以快速查找到x的偏移

当它增加了y, 隐藏类转变成C2, 通过C2可以快速查找到y的偏移

十二. JS代码的性能

前面我们看到V8有几个主要的优化点,

根据这些优化点可以想象JS编码如果顺应这些优化点,

如果JS中有部分代码需要重点提升性能,

可以考虑使用如下方法.

对象的属性顺序确定就更容易获得隐藏类的优化

实例化之后再添加删除属性将触发隐藏类的更改,

所以最好一次就完成实例化其后尽量减少对属性的修改

重复的执行一个函数,

相比重复的顺序执行多个不同函数运行更快

避免键值不是自增的数字,  避免预分配大数组,

最好是按需从小到大的增长不要删除数组中段的元素,

这会使被优化成native数组的对象变得稀疏.

十三. V8的创建和销毁

  1. int main(int argc, char* argv[]) {

  2. v8::V8::InitializeICU();

  3. v8::Platform* platform = v8::platform::CreateDefaultPlatform();

  4. v8::V8::InitializePlatform(platform);

  5. v8::V8::Initialize();

  6. v8::V8::SetFlagsFromCommandLine(&argc, argv, true);

  7. v8::Isolate* isolate = v8::Isolate::New();

  8. v8::Isolate::Scope isolate_scope(isolate);

  9. v8::HandleScope handle_scope(isolate);

  10. v8::Handle<v8::Context> context = CreateShellContext(isolate);

  11. v8::Context::Scope context_scope(context);

  12. ...

  13. v8::V8::Dispose();

  14. v8::V8::ShutdownPlatform();

  15. delete g_platform;

  16. }

上面的代码中, 

ICU  表示国际化字符处理

Platform  管理线程池和任务队列

Isolate  V8引擎实例

Isolate::Scope  确保Isolate单独执行

handle  对当前对象引用计数用于垃圾回收

context  是函数和对象的执行环境使得不同JS代码之间的运行不干扰一个Isolate可以运行多个context

Context::Scope   确保context单独执行

十四. V8的对象句柄

Handle提供了一个JS 对象在堆内存中的地址的引用.

V8垃圾回收器将回收一个已无法被访问到的对象占用的堆内存空间.

垃圾回收过程中回收器通常会将对象在堆内存中进行移动.

当回收器移动对象的同时也会将所有相应的Handle更新为新的地址.

当一个对象在JavaScript中无法被访问到,

并且也没有任何Handle引用它则这个对象将被当作"垃圾"对待.

回收器将不断将所有判定为"垃圾"的对象从堆内存中移除.

V8的垃圾回收机制是其性能的关键所在.

电影<<寻梦环游记>>中有一个片段叫猪皮哥的鬼魂听完一曲歌声后魂飞魄散.

解释是当活人的世界里如果没有一个人活人这个鬼魂他就会消失.

如果这是真的那么鬼魂的世界里就有一个垃圾回收器通过计数来回收鬼魂.

上图中除了persistenthandle需要手动调用Reset释放计数,

其它handle都是在程序运行到它作用域之外时自动释放计数

十五. V8 template对象

  1. v8::Handle<v8::Context> CreateShellContext(v8::Isolate* isolate) {

  2. v8::Handle<v8::ObjectTemplate> temp = v8::ObjectTemplate::New(isolate);

  3. temp->Set(v8::String::NewFromUtf8(isolate, "print"),

  4. v8::FunctionTemplate::New(isolate, Print));

  5. temp->Set(v8::String::NewFromUtf8(isolate, "read"),

  6. v8::FunctionTemplate::New(isolate, Read));

  7. temp->Set(v8::String::NewFromUtf8(isolate, "quit"),

  8. v8::FunctionTemplate::New(isolate, Quit));

  9. temp->Set(v8::String::NewFromUtf8(isolate, "version"),

  10. v8::FunctionTemplate::New(isolate, Version));

  11. return v8::Context::New(isolate, NULL, temp);

  12. }

在一个context,

template是 JavaScript函数和对象的模型.

你可以使用template来将 C++ 函数和数据结构封装在一个JavaScript对象中,

这样它就可以被JS 代码操作

通过set可以绑定函数到JS对象.

Chrome使用 template将 C++DOM 节点封装成JS 对象.

JsEngine也使用template将 C++函数封装到JS对象.

template如果是设置在global命名空间,

则可以被多个context复用.

另外template还可以通过SetAccessor绑定GetSet,

可以在JS访问到native中的属性.

设置拦截则是在属性在JS中被访问时调用

  1. v8::Handle<v8::Context> CreateShellContext(v8::Isolate* isolate) {

  2. v8::Handle<v8::ObjectTemplate> temp = v8::ObjectTemplate::New(isolate);

  3. temp->SetAccessor(String::NewFromUtf8(isolate, "y"), YGetter, YSetter);

  4. temp->SetNamedPropertyHandler(MapGet, MapSet);

  5. return v8::Context::New(isolate, NULL, temp);

  6. }

十六. 执行和异常

  1. v8::Local<v8::Context> context = v8::Local<v8::Context>::New(isolate_, context_);

  2. v8::Context::Scope context_scope(context);

  3. v8::Handle<v8::String> basescript = v8::String::NewFromUtf8(isolate_, strJsContent.c_str());

  4. v8::Local<v8::Script> compiled_script = v8::Script::Compile(basescript);

  5. TryCatch trycatch(isolate);

  6. Local<Value> v = compiled_script ->Run();

  7. if (v.IsEmpty()) {

  8. Local<Value> exception = trycatch.Exception();

  9. String::Utf8Value exception_str(exception);

  10. printf("Exception: %s\n", *exception_str);

  11. }

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多