精华内容
下载资源
问答
  • V8引擎

    2021-03-28 00:53:37
    新版本的V8 parser 解析器 生成AST抽象语法树 interpreter 解释器 Ignition 生成byteCode字节码 并直接执行 清除AST 释放内存空间 得到25% - 50%的等效机器代码大小 compiler 运行过程中,解释器收集优化信息发送给...

    转至: myth小艾(b站)
    https://www.bilibili.com/video/BV1zV411z7RX


    新版本的V8

    1. parser 解析器 生成AST抽象语法树
    2. interpreter 解释器 Ignition 生成byteCode字节码 并直接执行
    3. 清除AST 释放内存空间
    4. 得到25% - 50%的等效机器代码大小
    5. compiler 运行过程中,解释器收集优化信息发送给编译器TurboFan
    6. 重新生成机器码
    7. 有些热点函数变更会由优化后的机器码还原成字节码 也就是deoptimization 回退字节码操作执行

    优化点:

    1. 值声明未调用,不会被解析生成AST
    2. 函数只被调用一次,bytcode直接被解释执行,不会进入到编译优化阶段
    3. 函数被调用多次,Igniton会收集函数类型信息,可能会被标记为热点函数,可能被编译成优化后的机器代码

    好处:

    1. 由于一开始不需要直接编译成机器码,生成了中间层的字节码,从而节约了时间
    2. 优化编译阶段,不需要从源码重新解析,直接通过字节码进行优化,也可以deoptimization回退操作
    function sum(x,y){return x + y};
    sum(1,2);
    sum(3,4);
    sum(5,6);
    sum("7","8");//会回退字节码操作执行
    

    哪些程序用到V8

    • Chrome浏览器的JS引擎是V8
    • Nodejs的运行时环境是V8
    • electron的底层引擎是V8 【ɪˈlektrɑːn】 跨平台桌面应用开发工具

    blink是渲染引擎,V8是JS引擎

    访问Dom的接口是由Blink提供的

    功能

    接收JavaScript代码,编译代码后执行C++程序,编译后的代码可以在多种操作系统多种处理器上运行。

    1. 编译和执行JS代码
    2. 处理调用栈
    3. 内存分配
    4. 垃圾回收

    V8的js编译和执行

    • 解析器 parser js --> 解析成功抽象语法树AST
    • 解释器 interpreter AST --> 字节码bytecode,也有直接执行字节码的能力
    • 编译器 compiler bytecode --> 更高效的机器码

    V8版本5.9之前没有解释器,但是有两个编译器

    5.9版本的V8

    1. parser 解释器生成抽象语法树AST
    2. compiler 编译器Full-codegen 基准编译器 直接生成机器码
    3. 运行一段时间后,由分析器线程优化js代码
    4. compiler 编译器CrankShaft 优化编译器 重新生成AST提升运行效率

    这样设计的缺点

    1. 机器码会占用大量的内存
    2. 缺少中间层机器码,无法实现一些优化策略
    3. 无法很好的支持和优化JS的新语特性,无法拥抱未来
    展开全文
  • 本篇教程介绍了JavaScript V8引擎基础教程,希望阅读本篇文章以后大家有所收获,帮助大家对JavaScript的理解更加深入。<一、浏览器内核—渲染引擎渲染,就是根据描述或者定义构建数学模型,生成图像的过程。...

    本篇教程介绍了JavaScript V8引擎基础教程,希望阅读本篇文章以后大家有所收获,帮助大家对JavaScript的理解更加深入。

    <

    一、浏览器内核—渲染引擎

    渲染,就是根据描述或者定义构建数学模型,生成图像的过程。

    浏览器内核主要的作用是将页面转变成可视化/可听化的多媒体结果,通常也被称为渲染引擎。将HTML/CSS/JavaScript文本及其他相应的媒体类型资源文件转换成网页。

    上图中实线框内模块是所有移植的共有部分,虚线框内不同的厂商可以自己实现。下面进行介绍:

    WebCore 是各个浏览器使用的共享部分,包括HTML解析器、CSS解析器、DOM和SVG等。

    JavaScriptCore是WebKit的默认引擎,在谷歌系列产品中被替换为V8引擎。

    WebKit Ports是WebKit中的非共享部分,由于平台差异、第三方库和需求的不同等原因,不同的移植导致了WebKit不同版本行为不一致,它是不同浏览器性能和功能差异的关键部分。

    WebKit嵌入式编程接口,供浏览器调用,与移植密切相关,不同的移植有不同的接口规范。

    浏览器内核的层次结构,渲染引擎解析网页资源,调用第三方库进行渲染绘制。

    从上面两图可以大概看出渲染引擎的内部组成和大模块。WebCore,负责解析HTML、CSS生成DOM树等渲染进程,js引擎负责解析和执行js逻辑进程。

    二、JavaScript引擎

    1、JS引擎与渲染引擎关系

    JavaScript引擎和渲染引擎的关系如上图所示。渲染引擎使用JS引擎的接口来处理逻辑代码并获取结果。JS引擎通过桥接接口访问渲染引擎中的DOM及CSSOM(性能低下)。

    2、JS引擎工作流程

    JavaScript本质上是一种解释型语言,与编译型语言不同的是它需要一遍执行一边解析,而编译型语言在执行时已经完成编译,可直接执行,有更快的执行速度(如上图所示)。

    上图描述了JS代码执行的过程。具体过程先不看,一个JS引擎主要包括以下几个部分:

    编译器。将源代码经过词法分析,语法分析(你不知的js待查)编译成抽象语法树,在某些引擎中还包含将抽象语法树转化成字节码。

    解释器。在某些引擎中,解释器主要是接收字节码,解释执行这个字节码,同时也依赖垃圾回收机制等。

    JIT工具。将字节码或者抽象语法树转换成本地代码。

    垃圾回收器和分析工具(Profiler)。负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能和功效。

    三、V8 编译与执行

    1、数据表示

    Js语言中,只有基本数据类型Boolean、Number、String、Null、Undefined,其他都是对象。

    在V8中,数据的表示分成两个部分。第一个部分是数据的实际内容,它们是变长的,而且内容的类型也不一样,如String、对象等;第二部分是数据的句柄,大小是固定的,包含指向第一部分数据的指针。除了极少数的数据例如整型数据,其他的内容都是从堆中申请内存来存储,因为句柄本身能够存储整型,同时也能快速访问。

    2、句柄Handle

    V8需要进行垃圾回收,并需要移动这些数据内容,如果直接使用指针的话就会出问题或者需要比较大的开销。使用句柄就不存在这些问题,只需要修改句柄中的指针即可,使用者使用的还是句柄,它本身没有发生变化。

    由上图可以看出,一个Handle对象的大小是4字节(32位机器)或者8字节(64位机器);不同于JavascriptCore引擎,后者是使用8个字节来表示数据的句柄。小整数(只有31位可以使用)直接在Value_中获取值,而无须从堆中分配。

    因为堆中存放的对象都是4字节对齐的,所以指向它们的指针的最后两位都是00,这两位其实是不需要的。在V8中,它们被用来表示句柄中包含数据的类型。

    对象句柄的实现在V8中包含3个成员。第一个是隐藏类指针,为对象创建的隐藏类;第二个指向这个对象包含的属性值;第三个指向这个对象包含的元素。

    3、编译过程

    包括两个阶段:编译和执行。还有一个重要的特点就是延迟思想,使得很多js代码的编译直到运行时被调用才会发生(以函数单位),减少时间开销。

    Js代码经过编译器,生成抽象语法树,再通过JIT全代码生成器直接生成本地代码。减少抽象树到字节码的转换时间。

    生成本地代码后,为了性能考虑,通过 数据分析器 来采集一些信息,以帮助决策哪些本地代码需要优化,以生成效率更高的本地代码,这是一个逐步改进的过程。

    4、优化回滚

    编译器会做比较乐观和大胆的预测,就是认为这些代码比较稳定,变量类型不会发生改变,来生成高效的本地代码。当引擎发现一些变量的类型已经发生变化的时候,V8会将优化回滚到之前的一般情况。

    如上,函数ABC被调用很多次之后,数据分析器认为函数内的代码的类型都已经被获知了,但是当对于unkonwn的变量发生赋值是,V8只能将代码回滚到一个通用的状态。

    优化回滚是一个很费时的操作,而且会将之前优化的代码恢复到一个没有特别优化的代码,这是一个非常不高效的过程。

    5、隐藏类和内嵌缓存

    V8使用类和偏移位置思想,将本来需要字符串匹配来查找属性值的算法改进为,使用类似C++编译器的偏移位置的机制来实现,这就是隐藏类。

    隐藏类根据对象拥有相同的属性名和属性值的情况,分为不同的组(类型)。对于相同的组,将这些属性名和对应的偏移位置保存在一个隐藏类中,组内的对象共享该信息。同时,也可以识别属性不同的对象。

    如图,使用构造函数创建了两个对象a、b。这两个对象包含相同的属性名,在V8中它们被归为同一个组,也就是隐藏类,这些属性在隐藏类中有相同的偏移值。对象a、b可以共享这个分组的信息,当访问这些对象的时候,根据隐藏类的偏移值就可以知道它们的位置并进行访问。

    因为JavaScript是动态类型语言,所以当加入代码 d.z = 2。那么b对象所对应的将是一个新的隐藏类,这样a、b将属于不同的组。

    function add(a) { return a.x };

    访问对象属性基本过程为:获取隐藏类的地址,根据属性名查找偏移值,计算该属性的堆内存地址。这一过程还是比较耗费时间,实际上会用到缓存机制,叫做内嵌缓存。

    基本思想是将使用之前查找的结果(隐藏类和偏移值)缓存起来,再次访问时可以避免多次哈希表查找的问题。

    注意:当对象内的属性值出现多个类型是,那么缓存失误的概率就会高很多。退回到之前的方式来查找哈希表。

    四、V8内存分配

    主要讲两点:1、内存的划分使用2、对于JS代码的垃圾回收机制

    1、小内存区块Zone类

    管理一系列的小块内存,这些小内存的生命周期类似,可以使用一个Zone对象。

    Zone对象先对自己申请一块内存,然后管理和分配一些小内存。当一块小内存被分配之后,不能被Zone回收,只能一次性回收Zone分配的所有小内存。例如:抽象语法树的内存分配和使用,在构建之后,会生成本地代码,然后其内存被一次性全部收回,效率非常高。

    但是有一个严重的缺陷,当一个过程需要很多内存,Zone将需要分配大量的内存,却又不能及时回收,会导致内存不足情况。

    2、堆内存

    V8使用堆来管理JavaScript使用的数据、以及生成的代码、哈希表等。为了更方便地实现垃圾回收,同很多虚拟机一样,V8将堆分成三个部分。年轻代、年老代、和大对象。

    年轻分代:为新创建的对象分配内存空间,经常需要进行垃圾回收。为方便年轻分代中的内容回收,可再将年轻分代分为两半,一半用来分配,另一半在回收时负责将之前还需要保留的对象复制过来。

    年老分代:根据需要将年老的对象、指针、代码等数据保存起来,较少地进行垃圾回收。

    大对象:为那些需要使用较多内存对象分配内存,当然同样可能包含数据和代码等分配的内存,一个页面只分配一个对象。

    当在代码中声明变量并赋值时,所使用对象的内存就分配在堆中。如果已申请的堆空闲内存不够分配新的对象,将继续申请堆内存,知道堆的大小达到V8的限制为止。

    V8的内存使用限制:64位系统中约为1.4G,32位系统中约为0.7G。在浏览器页面中足够使用;在node中则会有不足,导致Node无法直接操作大内存对象,在打个node进程的情况下,无法充分利用计算机的内存资源。

    内存的限制有两方面原因,防止浏览器的一个页面占用太多系统内存资源,另一方面,V8垃圾回收的效率问题。对于1.5G内存,做一次小的垃圾回收需要50毫秒以上,做一次全量的回收甚至要1秒以上,这个过程会阻塞JS线程的执行。

    五、垃圾回收

    V8的垃圾回收策略主要基于分代式回收机制。按对象的存活时间将内存的垃圾回收进行不同的分代,然后分别对不同的内存使用更高效的算法。

    1、新生代Scavenge(清除)算法

    主要采用Cheney(人名)算法。一种采用复制的方式实现的垃圾回收算法。

    1、将新生代堆内存分一为二,每一部分空间称为semispace。其中一个处于使用之中的称为from空间,另一个处于闲置称为to空间。

    2、当我们分配对象时,先是在From空间中进行分配。

    3、垃圾回收时,检查from空间内的存活对象,一是否经历过清除回收,二to空间是否已经使用了25%(保证新分配有足够的空间)。

    .    4、将这些存活对象复制到to空间中。非存活对象占用的空间将会被释放。

    5、完成复制后,from空间与to空间角色发生对换。

    注:实际使用的堆内存是新生代中的两个semispace空间大小,和老生代所用内存大小之和。

    如何判断对象是否存活呢?作用域?是一套存储和查询变量的规则。这套规则决定了内存里对象能否访问。

    特点:

    清除算法是典型的牺牲空间换取时间的算法,无法大规模地应用到所有回收中,却非常适合应用在新生代生命周期短的变量。

    2、Mark-Sweep老生代标记清除

    1、  标记阶段遍历堆中的所有对象,并标记活着的对象

    2、  清除阶段,只清除没有被标记的对象。

    最大的问题是,在进行一次标记清除之后会出现不连续的状态。这种内存碎片会对后续的内存分配造成问题。很可能需要分配一个大对象时,所有的碎片空间都无法完成,就会提前触发垃圾回收,而这次全量回收是不必要的。

    3、Mark-Compact老生代标记整理

    在标记清除的基础上发展而来,在整理的过程中

    1、  将活着的对象往一段移动

    2、  移动完成后,直接清理掉边界外的内存

    4、Incremental Marking增量标记

    垃圾回收的过程都需要将应用逻辑暂停下来。

    为了降低全量回收带来的停顿时间,在标记阶段,将原本一口气要完成的动作改为增量标记。垃圾回收与应用逻辑交替执行到标记阶段完成。最大停顿时间较少的1/6左右.

    后续还引入了延迟清理与增量整理,让清理和整理动作也变成增量式的。

    六、高效内存使用

    1、作用域

    函数在每次被调用时会创建对应的作用域(自身的执行环境,和变量对象),作用域中声明的局部变量分配在改作用域中。执行结束后,作用域销毁,做死亡标记,在下次垃圾回收时被释放。

    变量的主动释放,解除引用关系:

    如果变量是全局变量,由于全局作用域需要直到进程退出才能释放,导致引用的对象常驻内存(老生代)。可以通过delete来删除引用关系,或者将变量重新赋值。但是在V8中通过delete删除对象的属性有可能干扰V8的优化。

    闭包:

    当函数执行结束,但作用域(变量对象)仍需要被引用时,其变量对象不能被标记失效,占用的内存空间不会得到清除释放。除非不再有引用,才会逐步释放。

    在正常的js执行中,无法立即回收的内存有闭包和全局变量这两种情况。要避免这些变量无限制地增加,导致老生代中的对象增多,甚至内存泄漏。

    2、内存泄漏

    实质:应当回收的对象出现意外,而没有被回收,变成了常驻在老生代中的对象。

    通常,造成内存泄漏的原因有如下:

    1、  缓存

    缓存无限制增长,长期存在于老生代,占据大量内存。

    策略:对缓存增加数量和有效期限制。

    使用Redis等进程外缓存,不占用V8缓存限制,进程间可以共享缓存。

    2、  队列消费不及时

    Js可以通过队列(数组对象)来完成许多特殊的需求。在消费者—生产者模型中经常充当中间产物。当消费速度低于生成速度时,将会形成堆积。

    如:日志记录,采用低效率的数据库写入时,可能会是写入事件堆积。

    策略:使用高效的消费方式。监控队列的长度。

    3、  作用域得不到释放

    大量闭包的使用,要注意释放作用域。

    本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标WEB前端JavaScript频道!

    展开全文
  • Google V8引擎浅析

    2021-10-29 00:21:48
    前端开发人员都会遇到一个流行词:V8。它的流行程度很大一部分是因为它将JavaScript的性能提升到了一个新的水平。是的,V8很快。但它是如何发挥它的魔力?前言源代码:https://s...

    前端开发人员都会遇到一个流行词:V8。它的流行程度很大一部分是因为它将JavaScript的性能提升到了一个新的水平。是的,V8很快。但它是如何发挥它的魔力?

    前言

    3b6162e145bca1166aed67bf3f555ef2.png
    源代码:https://source.chromium.org/chromium/chromium/src/+/master:v8/ [1]
        在介绍V8引擎之前,我们可以先分析下为什么需要V8引擎?众所周知,前端最火的开发语言非javascript莫属,那javascript与V8是什么样的关系呢?
    我们知道,计算机只能识别二进制的机器语言,无法识别更高级的语言,用高级的语言开发,需要先将这些语言翻译成机器语言,而语言种类大体可以分为解释型语言和编译型语言:

    语言种类翻译过程优点不足常见语言例子
    解释型语言解释器 > 翻译成与平台无关的中间代码与平台无关,跨平台性强每次都需要解释执行
    需要源文件
    按句执行,执行效率差
    javascript、Ruby、Python
    编译型语言预处理>编译>汇编>可执行的二进制文件一次编译,永久执行
    无需源代码,只需要可执行的源文件
    运行速度快
    不同系统可识别的二进制文件不同,跨平台兼容性差C、C++、java

        JavaScript就是一种解释型语言,支持动态类型、弱类型、基于原型的语言,内置支持类型。一般JavaScript都是在前端侧执行,需要能快速响应用户,所以这就要求语言本身可以被快速地解析和执行,javascript引擎就为此目的而生。

    JS引擎历史那些事

        1993年网景浏览器诞生,成为浏览器鼻祖。

        1995年微软推出了IE浏览器,拉开了第一次浏览器大战的序幕。IE受益于windows系统风靡世界,逐渐占有了大部分市场。

        1998年处于低谷的网景公司成立了Mozilla基金会,在该基金会推动下,开发了著名的开源项目Firefox并在2004年发布1.0版本,拉开了第二次浏览器大战的序幕,IE发展更新较缓慢,Firefox一推出就深受大家的喜爱,市场份额一直上升。

        2003年,苹果发布了Safari浏览器,并在2005年释放了浏览器中一种非常重要部件的源代码,发起了一个新的开源项目WebKit。

        2008年,Google以苹果开源项目WebKit作为内核,创建了一个新的项目Chromium,在Chromium的基础上,Google发布了Chrome浏览器。Google工程师起初曾考虑过使用 Firefox 的 Gecko 内核,然而他们最终被 Android 团队说服采用了 WebKit,他们劝说道:WebKit 轻快、易扩展、代码结构清晰。而且在苹果公司内部不停的速度压榨的情况下,最终才发布了WebKit并把它开源。https://www.google.com/googlebooks/chrome/med_14.html [2]

        站在当下回头来看,当时的选择无比的明智,现在WebKit 更是出现在几乎每一个移动平台——iOS、Android、BlackBerry等等。可惜的是,Google加入了WebKit之列并成为开发的主力后,独立fork出了Blink,自己的内核,又与WebKit分道扬镳了。

            在Blink基础之上,为了追求javascript的极致速度和性能,Google工程师又创造出来了V8引擎,而Node的作者认为这么优秀的引擎只在浏览器中跑可惜了,不如拿出来写一些配套的模块,就可以开发服务器端应用了,这就有了Node.js,至此javascript语言从单纯的前端语言,蜕变成了一门全端语言,从而有了全栈工程师的发展方向。

        微软依然维护着自己的EdgeHTML引擎,作为老的Trident引擎的替代方案。新的Edge的浏览器已经开始使用Chromium的Blink引擎了,当Edge加入Blink的阵营后,以Webkit和其衍生产品已经牢牢的占有市场的6成以上。

    0a9c4c74b9220ec2c1430224050fae3c.png

    什么是V8引擎

        V8引擎是一个JavaScript引擎实现,最初由一些语言方面专家设计,后被谷歌收购,随后谷歌对其进行了开源。V8使用C++开发,在运行JavaScript之前,相比其它的JavaScript的引擎转换成字节码或解释执行,V8将其编译成原生机器码(IA-32, x86-64, ARM, or MIPS CPUs),并且使用了如内联缓存(inline caching)等方法来提高性能。有了这些功能,JavaScript程序在V8引擎下的运行速度媲美二进制程序。V8支持众多操作系统,如windows、linux、android等,也支持其他硬件架构,如IA32,X64,ARM等,具有很好的可移植和跨平台特性。

    V8如何运行Javascript

    0c5e2f64dcd0a5d83841da33bf4455b6.png

    Loading

        js文件加载的过程并不是由V8负责的,它可能来自于网络请求、本地的cache或者是也可以是来自service worker,浏览器的js加载的整个过程,就是V8引擎运行js的前置步骤。
    3种加载方式 & V8的优化

    • Cold load: 首次加载脚本文件时,没有任何数据缓存

    • Warm load:V8分析到如果使用了相同的脚本文件,会将编译后的代码与脚本文件一起缓存到磁盘缓存中

    • Hot load: 当第三次加载相同的脚本文件时,V8可以从磁盘缓存中载入脚本,并且还能拿到上次加载时编译后的代码,这样可以避免完全从头开始解析和编译脚本

        延伸阅读:V8 6.6 进一步改进缓存性能[3] 代码缓存策略优化,简单讲就是从缓存代码依赖编译过程的模式,改变成两个过程解耦,并增加了可缓存的代码量,从而提升了解析和编译的时间
    【旧模式】
    66ed738ab79d547ff4045bb39d7737ef.png
     【新模式】
    0947238ea945509d5870b1d81b843cff.png

    Parsing

        Parsing(分析过程)是将js脚本转换成AST(抽象语法树:Abstract Syntax Tree)的过程

    词法分析

    Token

        从左往右逐个字符地扫描源代码,通过分析,产生一个不同的标记,这里的标记称为token,代表着源代码的最小单位,通俗讲就是将一段代码拆分成最小的不可再拆分的单元,这个过程称为词法标记。词法分析器常用的token标记种类有几类:

    • 常数(整数、小数、字符、字符串等)

    • 操作符(算术操作符、比较操作符、逻辑操作符)

    • 分隔符(逗号、分号、括号等)

    • 保留字

    • 标识符(变量名、函数名、类名等)

    TOKEN-TYPE TOKEN-VALUE\
    -----------------------------------------------\
    T_IF                 if\
    T_WHILE              while\
    T_ASSIGN             =\
    T_GREATTHAN          >\
    T_GREATEQUAL         >=\
    T_IDENTIFIER name    / numTickets / ...\
    T_INTEGERCONSTANT    100 / 1 / 12 / ....\
    T_STRINGCONSTANT     "This is a string" / "hello" / ...
    流式处理

        词法处理过程中,输入是字节流,输出是Token流:
    964563d6386e9f43617936dc0d6e6eff.png
        定义100行单词处理时间1t,占用内存1m,来对比看下流式和非流式两种方式的区别:

    • 非流式处理: 时间消耗6t,内存消耗峰值3M,读取+处理完成才会释放内存块;

    • 流式处理: 时间消耗是4t,内存消耗峰值1M,内存释放相同机制;

        可以发现流式处理能很大程度上提升处理效率和节省内存空间
    db7bd8c9d7fbd68ab969283060e462ec.png

    词法分析器结构

    e283d78a019c86382390606e111e8f68.png
        扫描缓存区大小一般是固定的大小,但也无法确保能不影响单词的边界,同时会使用两个指示器(指针),一个指向正在识别的单词头部,一个向前搜索单词的终点。
    8d490b63bf5c13db05f9f1a0be50c767.png
        这里有个概念是:超前搜索,用户可能把关键字等特殊的语句重新定义,所以扫描器需要提前扫描到这些字后面的代码格式,才能确定其最终代表的词性。有这个问题存在会影响到性能,现在的大多数语言会把基本字都设置成保留字,用户不能修改其含义。

    有限状态机

        扫描器依托有限状态机来实现正则匹配不同的token类型,下面以识别整数和浮点数的状态机为例,简单讲下过程:
    单圆代表着“结点”,代表着扫描过程中可能出现的“状态”,也就是上面提到的两个指示器中间对应的所有字符;
    箭头指向可称为“边”,从一个状态指向另外一个状态,边的标号代表一个或者多个符号,如果能匹配一条边,向前指针就会前移,指向下个状态
    Start代表着开始状态
    双圆环代表着接受状态或者最终状态,代表着已经找到了准确的状态,向语法分析器返回一个token和相关的属性值
    “*”代表着可能会识别到并不包含接受状态的符号,可能指针需要回退一步或者多步

    词法单元模式
    digit[0-9]
    digitsdigit+
    numberdigits(.digits)?(E[+-]?digits)?

        23状态对应的是识别为整数,24状态代表为非科学计数法的浮点数,22匹配的是科学计数法的浮点数(包含整数和小数部分),同时也有只有整数部分的。状态机会通过一个state参数来保存识别出来的编号(例如:0-24),然后通过switch 来判断state的值,实现不同状态对应的执行动作。
    d70f6009d2f281f9ff3ddc81c8829edb.png
    状态机是一个很有用的设计思想,在很多场景都有使用,大家可以下来好好学习下,React的state,Redux的状态管理等等。
    85ec2ffaa5a930c23222a8705888629c.png

    在线demo

    Esprima: Parser[4] 在线demo,仅做参考,v8的解析的比这个复杂:https://v8.dev/blog/scanner [5]
    b72cb99e05fd174a357f646f1bd4c062.png

    语法分析

        语法分析是指根据某种给定的形式文法对由单词序列构成的输入文本,例如上个阶段的词法分析产物-tokens stream,进行分析并确定其语法结构的过程。
        抽象语法树(Abstract Syntax Tree) 是源代码结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
        推荐这2个网站可以用来分析代码生成的ast结构:Esprima: Parser[6]
    https://resources.jointjs.com/demos/javascript-ast [7]
    以下面代码为例,简单分析下对应生成的AST树形结构:

    function f(a, b) {
    let result = 0;
    if(a > 0) {
    result = a + b;
    } else {
    result = a - b;
    }
    return result
    }


    abc2efd1b65746b60e4c812a3f9a364d.png
    67fbe33407ebfe2ef521b724f4d14d64.png
    V8会将语法分析的过程分为两个阶段来执行:

    Pre-parser
    • 跳过还未使用的代码

    • 不会生成对应的ast,会产生不带有变量的引用和声明的scopes信息

    • 解析速度会是Full-parser的2倍

    • 根据js的语法规则仅抛出一些特定的错误信息

    Full-parser
    • 解析那些使用的代码

    • 生成对应的ast

    • 产生具体的scopes信息,带有变量引用和声明等信息

    • 抛出所有的js语法错误

    默认函数声明后,没有调用,对应的pre-parser:

    ➜ Desktop d8 test.js --print-ast
    [generating bytecode for function: ]
    --- AST ---
    FUNC at 0
    . KIND 0
    . LITERAL ID 0
    . SUSPEND COUNT 0
    . NAME ""
    . INFERRED NAME ""
    . DECLS
    . . FUNCTION "f" = function f

    在文件末尾增加f(1, 2)调用函数方法,就可以触发Full-parser过程:

    [generating bytecode for function: f]
    --- AST ---
    FUNC at 10
    . KIND 0
    . LITERAL ID 1
    . SUSPEND COUNT 0
    . NAME "f"
    . PARAMS
    . . VAR (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
    . . VAR (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
    . DECLS
    . . VARIABLE (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
    . . VARIABLE (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
    . . VARIABLE (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
    . BLOCK NOCOMPLETIONS at -1
    . . EXPRESSION STATEMENT at 25
    . . . INIT at 25
    . . . . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
    . . . . LITERAL undefined
    . IF at 35
    . . CONDITION at 40
    . . . GT at 40
    . . . . VAR PROXY parameter[0] (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
    . . . . LITERAL 0
    . . THEN at -1
    . . . BLOCK at -1
    . . . . EXPRESSION STATEMENT at 51
    . . . . . ASSIGN at 58
    . . . . . . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
    . . . . . . ADD at 63
    . . . . . . . VAR PROXY parameter[0] (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
    . . . . . . . VAR PROXY parameter[1] (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
    . . ELSE at -1
    . . . BLOCK at -1
    . . . . EXPRESSION STATEMENT at 83
    . . . . . ASSIGN at 90
    . . . . . . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
    . . . . . . SUB at 94
    . . . . . . . VAR PROXY parameter[0] (0x7f80dd00fad8) (mode = VAR, assigned = false) "a"
    . . . . . . . VAR PROXY parameter[1] (0x7f80dd00fb80) (mode = VAR, assigned = false) "b"
    . RETURN at 105
    . . VAR PROXY local[0] (0x7f80dd00fc28) (mode = LET, assigned = true) "result"
    为什么要做两次解析?

        如果仅有一次,那必须是Full-parser,但这样的话,大量未使用的代码会消耗非常多的解析时间,结合具体的项目来看下:通过Devtools-Coverage录制的方式可以分析页面哪些代码没有用到:
    942989ae92f3a74426b35c232e45880f.png
        两次解析的负面影响:如果部分代码片段已经被pre-parser过了,那么在执行的过程中还会经过一次Full-parser,那总体耗时就是0.5*parser + 1 * parser = 1.5parser。
    下面罗列了不同种情况的代码声明对应的解析方法:

    let a = 0; *// Top-Level 顶层的代码都是 Full-Parsing*
    *// 立即执行函数表达式 IIFE = Immediately Invoked Function Expression*
    (function eager() {...})(); *// 函数体是 Full-Parsing*
    *// 顶层的函数非IIFE*
    function lazy() {...} *// 函数体是 Pre-Parsing*
    lazy(); *// -> Full-Parsing 开始解析和编译!*
    *// 强制触发Full-Parsing解析*
    !function eager2() {...}, function eager3() {...} *// full 解析*
    let f1 = function lazy() { ... }; *// 函数体是 Pre-Parsing*
    let f2 = function lazy() {...}(); *// 先触发了pre 解析, 然后又full解析*

    Bad case: 深度内嵌定义方法

    function lazy_outer() { *// pre-parser*
    function inner() {
    function inner2() {
    *// ...*
    }
    }
    }
    lazy_outer(); *// pre-parsing inner & inner2*
    inner(); *// pre-parsing inner & inner2 (3rd time!)*
    Parser性能优化Tips
    1. 使用Devtools-Coverage工具分析网页无用代码,并进行裁剪

    2. V8会将Parser过程的结果缓存72个小时,当用到的脚本文件中有任何代码修改了,那么parser缓存的结果就失效了,所以好的实践是将经常变动的代码打包一起,非经常变动的代码打包一起,这就有了第三方库dll的方案。

    3. 可以考虑懒加载方案,当有一些代码无需业务代码执行的最开始需要的话,可以用异步加载的方法后续再加载

    Interpret(解释)

    b006892ae0384a0e25efce03ca6d92c2.png
        解释阶段会将之前生成的AST转换成字节码,代码会被编译器编译成从未被优化过的机器码,在运行的过程中,会将需要优化的代码进行热点标记,再通过更高级的编译器进行优化后再编译。
        增加字节码的好处是,并不是将AST直接翻译成机器码,因为对应的cpu系统会不一致,翻译成机器码时要结合每种cpu底层的指令集,这样实现起来代码复杂度会非常高。
    e9220df0ff4b62b9f446330baaaed3da.png

    内存占用

        最开始V8的设计中,运行js代码后会将AST直接转换成二进制的机器码,由于执行机器码的效率是非常高效的,所以这种方式在发布后的一段时间内运行效果是非常好。

        但机器码会存储在内存中,退出进程后会存储在磁盘上,但如果js源码可能只有1M,但转换后的机器码可能会多达几十M,过度占用会导致性能大大降低。当手机越来变得普及后,内存问题就更加突出了。
    2cca8fa05f3c57f7f65f1809008685eb.png

    Ignition解释器

        V8团队为了解决这类性能问题,自己做出了Ignition[8]解释器,在中间过程中增加了字节码
        Ignition解释器转换成的字节码,比传统的直接翻译成机器码节省了 25%-50% 的内存空间,同时为了进一步节省,当字节码生成后,AST的数据就直接被废弃掉了。在字节码上又加上了一些元数据,例如记录源代码的位置和用于执行字节码的处理方法等。
    49346168d0df48d03dd23db3599f4aa0.png
    还是以上面的代码为例,看下d8下的字节码

    function add(x, y) {
    var z = x + y;
    return z;
    }
    add(1,2);
    ➜ Desktop d8 test.js --print-bytecode
    [generated bytecode for function: (0x3ae908292f71 <SharedFunctionInfo>)]
    Bytecode length: 28
    Parameter count 1
    Register count 4
    Frame size 32
    OSR nesting level: 0
    Bytecode Age: 0
    0x3ae908293032 @ 0 : 13 00 LdaConstant [0]
    0x3ae908293034 @ 2 : c2 Star1
    0x3ae908293035 @ 3 : 19 fe f8 Mov <closure>, r2
    0x3ae908293038 @ 6 : 64 4f 01 f9 02 CallRuntime [DeclareGlobals], r1-r2
    0x3ae90829303d @ 11 : 21 01 00 LdaGlobal [1], [0]
    0x3ae908293040 @ 14 : c2 Star1
    0x3ae908293041 @ 15 : 0d 03 LdaSmi [3]
    0x3ae908293043 @ 17 : c1 Star2
    0x3ae908293044 @ 18 : 0d 04 LdaSmi [4]
    0x3ae908293046 @ 20 : c0 Star3
    0x3ae908293047 @ 21 : 62 f9 f8 f7 02 CallUndefinedReceiver2 r1, r2, r3, [2]
    0x3ae90829304c @ 26 : c3 Star0
    0x3ae90829304d @ 27 : a8 Return
    Constant pool (size = 2)
    0x3ae908293001: [FixedArray] in OldSpace
    - map: 0x3ae908002205 <Map>
    - length: 2
    0: 0x3ae908292fb9 <FixedArray[2]>
    1: 0x3ae908003f85 <String[3]: #add>
    Handler Table (size = 0)
    Source Position Table (size = 0)
    [generated bytecode for function: add (0x3ae908292fc9 <SharedFunctionInfo add>)]
    Bytecode length: 7
    Parameter count 3
    Register count 1
    Frame size 8
    OSR nesting level: 0
    Bytecode Age: 0
    0x3ae908293156 @ 0 : 0b 04 Ldar a1
    0x3ae908293158 @ 2 : 38 03 00 Add a0, [0]
    0x3ae90829315b @ 5 : c3 Star0
    0x3ae90829315c @ 6 : a8 Return
    Constant pool (size = 0)
    Handler Table (size = 0)
    Source Position Table (size = 0)

        V8在执行字节码的过程中,使用到了通用寄存器累加寄存器,函数参数和局部变量保存在通用寄存器里面,累加器中保存中间计算结果,在执行指令的过程中,如果直接由cpu从内存中读取数据的话,比较影响程序执行的性能,使用寄存器存储中间数据的设计,可以大大提升cpu执行的速度。
        这里面包含了很多编译原理里面涉及到的指令集。
        字节码更多指令可以看下V8-Ignition的源码[9]
    8604a908bf2138a9bf376f7c4a6c820d.png
    参考文档:Google Ignition ppt[10]

    编译器

        这个过程主要指是V8的TurboFan编译器将字节码翻译成机器码的过程。
    字节码配合解释器和编译器这一技术设计,可以称为JIT,即时编译技术,java虚拟机也是类似的技术,解释器在解释执行字节码时,会收集代码信息,标记一些热点代码热点代码(hotspot)就是一段代码被重复执行多次,TurboFan会将热点代码直接编译成机器码,缓存起来,下次调用直接运行对应的二进制的机器码,加速执行速度。
    a27b8c22524c960c038619c75d46eacb.png
    TurboFan的整体优化过程,可参见下图,这里的优化分为了3层,更偏向于系统底层
    47eec5313336f073f0dbcf5956e8bd0c.png
    TurboFan
    将字节码编译成机器码的过程中,还进行了简化处:常量合并、强制折减、代数重新组合。
    6cf60c1134a3469e34356b0883a212a5.png
    类型推断(Speculative Optimization)是TurboFan的一大核心能力,

    Execution(执行)

        在Javascript的执行过程中,经常遇到的就是对象属性的访问。作为一种动态的语言,在js中,一行简单的属性访问可能包含着复杂的语义: Object.xxx的形式,可能是属性的直接访问,也可能调用的对象的Getter方法,还有可能是要通过原型链往上层对象中查找。
        这种不确定性而且动态判断的情况,会浪费很多查找时间,降低运行的速度,V8中会把第一次分析的结果放在缓存中,当再次访问相同的属性时,会优先从缓存中去取,调用GetProperty(Object, "xxx", feedback_cache)的方法获取缓存,如果有缓存结果,就会跳过查找过程。

    Object Shapes

        在静态语言中,代码执行前要先进行编译,编译的时候,每个对象的属性都是固定的, 直接可以通过记录某个属性相对该对象的地址的偏移量,就可直接读取到属性值, 而在动态语言中,对象的属性是会被实时改动的,能否可以借鉴静态语言的这种特点来设计呢?
        V8加入了Object Shapes 或者叫做Hidden Class(隐藏类)的概念。V8会给每个对象创建一个隐藏类,里面记录了对象的一些基本信息:

    • 对象所包含的所有属性

    • 每个属性相对于该对象的偏移量

        有了属性名和地址的偏移量,当访问对象的某个属性时,就可以直接从内存中读取到,不需要再经过一系列的查找,大大提升了V8访问对象时的效率。以代码为例:

    let demoObj = { a:1, b:2 }; %DebugPrint(demoObj); // d8内部api

    执行d8的调试命令查看对应的隐藏类结构:

    ➜ Desktop d8 --allow-natives-syntax test2.js
    DebugPrint: 0x2ef2081094b5: [JS_OBJECT_TYPE]
    - map: 0x2ef2082c78c1 <Map(HOLEY_ELEMENTS)> [FastProperties] //隐藏类地址
    - prototype: 0x2ef208284205 <Object map = 0x2ef2082c21b9> // 原型链
    - elements: 0x2ef20800222d <FixedArray[0]> [HOLEY_ELEMENTS] // elements 和 properties 快慢属性相关
    - properties: 0x2ef20800222d <FixedArray[0]>
    - All own properties (excluding elements): {
    0x2ef20808ecf9: [String] in ReadOnlySpace: #a: 1 (const data field 0), location: in-object
    0x2ef20808ed95: [String] in ReadOnlySpace: #b: 2 (const data field 1), location: in-object
    }
    0x2ef2082c78c1: [Map] // 对应的隐藏类
    - type: JS_OBJECT_TYPE
    - instance size: 20
    - inobject properties: 2
    - elements kind: HOLEY_ELEMENTS
    - unused property fields: 0
    - enum length: invalid
    - stable_map
    - back pointer: 0x2ef2082c7899 <Map(HOLEY_ELEMENTS)>
    - prototype_validity cell: 0x2ef208202405 <Cell value= 1>
    - instance descriptors (own) #2: 0x2ef2081094e5 <DescriptorArray[2]>
    - prototype: 0x2ef208284205 <Object map = 0x2ef2082c21b9>
    - constructor: 0x2ef208283e3d <JSFunction Object (sfi = 0x2ef208209071)>
    - dependent code: 0x2ef2080021b9 <Other heap object (WEAK_FIXED_ARRAY_TYPE)>
    - construction counter: 0

    隐藏类的复用

        现在我们清楚每个对象都有一个map属性,指向的是一个隐藏类,如果两个相同形状的对象,在V8中会复用同一个隐藏类,这样会减少创建隐藏类的次数,加快V8的执行速度,同时也会减少隐藏类占用的内存空间,相同形状的定义:

    • 相同的属性名称

    • 相同的属性名顺序一致

    • 相同的属性个数

    let demoObj = { a:1, b:2 }; 
    let demoObj2 = { a:100, b:200 }; 
    %DebugPrint(demoObj); 
    %DebugPrint(demoObj2);


    061612b1a55b2f386496d0a067a6dc17.png
        当对象的隐藏类创建完成后,一旦对象发生形状上的改变:增加新的属性或者是删除旧的属性时,隐藏类就会重新被创建,这个动作是V8执行过程中的一笔开销。所以这里的代码优化建议

    1. 尽量创建形状一致的对象,属性的顺序、属性的key值、key值的个数尽量保持一致

    2. 尽量不要后面再加入临时属性,声明对象时属性完整,因为每次加入属性,破坏了原有的对象形状,隐藏类要重新创建

    3. 尽量不要使用delete 删除对象属性,同理于上条原理,会破坏对象的形状

    总结

        V8引擎经历了多年的不断完善和新技术的演进,里面涵盖了太多的技术点和设计思想,本次分享主要是从js脚本内容下载到最终在V8引擎执行的过程进行分析,来体会一下V8的独到之处,和它不断追求极致性能的思想,加深对V8的理解,也能更好地写出高效的代码,而且很多优化思想也可以在其他的语言或框架中得到印证,还有很多比较深入的知识点,例如:内联属性策略、反馈向量机制、快慢属性等。

    参考文档

    浏览器工作原理-webkit内核研究[11]
    Using d8 · V8[12]
    V8 JavaScript engine[13] 官网
    认识 V8 引擎[14]
    [译] Blink内核是如何工作的?[15]
    词法分析--手动词法单元的识别(状态转换图、KMP算法)[16]
    v8 parser JS[17]
    利用 V8 深入理解 JavaScript 设计[18]
    [译] V8引擎中基于推测的优化介绍[19]
    JS引擎工作原理详解[20]

    参考资料

    [1]

    https://source.chromium.org/chromium/chromium/src/+/master:v8/ : https://source.chromium.org/chromium/chromium/src/+/master:v8/

    [2]

    https://www.google.com/googlebooks/chrome/med_14.html : https://www.google.com/googlebooks/chrome/med_14.html

    [3]

    V8 6.6 进一步改进缓存性能: https://zhuanlan.zhihu.com/p/36183010

    [4]

    Esprima: Parser: https://esprima.org/demo/parse.html#

    [5]

    https://v8.dev/blog/scanner : https://v8.dev/blog/scanner

    [6]

    Esprima: Parser: https://esprima.org/demo/parse.html#

    [7]

    https://resources.jointjs.com/demos/javascript-ast : https://resources.jointjs.com/demos/javascript-ast

    [8]

    Ignition: https://v8.dev/docs/ignition

    [9]

    源码: https://source.chromium.org/chromium/chromium/src/+/master:v8/src/interpreter/interpreter-generator.cc

    [10]

    Google Ignition ppt: https://docs.google.com/presentation/d/1HgDDXBYqCJNasBKBDf9szap1j4q4wnSHhOYpaNy5mHU/edit#slide=id.g1357e6d1a4_0_58

    [11]

    浏览器工作原理-webkit内核研究: https://juejin.cn/post/6844903569200513037

    [12]

    Using d8 · V8: https://v8.dev/docs/d8

    [13]

    V8 JavaScript engine: https://v8.dev/

    [14]

    认识 V8 引擎: https://zhuanlan.zhihu.com/p/27628685

    [15]

    [译] Blink内核是如何工作的?: https://juejin.cn/post/6844904143279095822

    [16]

    词法分析--手动词法单元的识别(状态转换图、KMP算法): https://wangwangok.github.io/2019/10/28/compiler_stateGraph_kmp/

    [17]

    v8 parser JS: https://mlib.wang/2020/02/08/v8-parser-compiler-javascript/

    [18]

    利用 V8 深入理解 JavaScript 设计: https://segmentfault.com/a/1190000040064998

    [19]

    [译] V8引擎中基于推测的优化介绍: https://zhuanlan.zhihu.com/p/51047561

    f2e393721a5fb56288dcf05f6f5d38f8.png

    关于奇舞团

    奇舞团是360集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

    6621178ea2f83b847a024583d69e4106.png

    展开全文
  • 看到这篇文章的时候,如果你也向我一样曾经对编译v8感到迷茫,不知道怎么才能正确编译好的话,我希望这篇文章能够帮助你实现v8引擎编译并且使用。我研究了两天时间,终于成功编译了v8代码(WIN10环境),发现编译v8...


    本篇文章的方法在2021-3-31测试正常使用。

    看到这篇文章的时候,如果你也向我一样曾经对编译v8感到迷茫,不知道怎么才能正确编译好的话,我希望这篇文章能够帮助你实现v8引擎编译并且使用。我研究了两天时间,终于成功编译了v8代码(WIN10环境),发现编译v8没有像网上说的那么困难(首先你需要一个网络代理),所以我决心想写一篇细致的文章解决目前大家编译中的问题,一些共犯的基础问题不再仔细阐述,重点写编译过程中的问题,如果你是在下载源码过程中遇到问题的话,一定要检查代理的问题。如果你是在linux等系统的话,建议仔细查看文档,文档里面linux介绍比win的要多。Linux系统编译成功的机会更大!所以如果实在没有办法,不妨在其他系统编译好之后复制到本系统使用。
    我特地让一个朋友测试,在没有阅读其他文章的时候,仅仅阅读本篇文章再加上官方文档也能成功安装V8引擎。所以不一定把每篇文章都读,一定要找到重点来看。

    如果你没有时间仔细阅读文章的话我把所有重点全部加粗的方式标注出来,方便快速读到重点。所谓重点其实就是你出错的原因,比如你gclient出错,那么就检查gclient之前的那一步,解决完再进行下面的一步。在执行最后一步之前,一定要保证前面的步骤全部正确。由于源码的head有用户电脑签名信息,所以不方便发布源码,大家还是自己动手gclient下载源码吧,看到这篇文章的人应该都是有一定技术的,下载源码这个是小事,之后如何编译,如何使用才是重点。

    1.编译前的配置工作

    1.1配置代理

    管理员的身份运行控制台(win+r键 cmd),之后输入以下:

    set HTTP_PROXY=http://127.0.0.1:8990
    set HTTPS_PROXY=http://127.0.0.1:8990
    

    其中的8990为你们自己设置的代理端口,有些程序默认为8080,视不同程序而定。只需要输入这两行代码就可以,用这种方式设置代理的话,重启控制台就会失效,需要重新设置。有了代理是之后的gclient得以正常运行的前提。

    1.2下载depot tools

    在下载工具之前首先要明确为何要下载这个工具?这个工具其实是便于下载谷歌源码的一个工具,直接使用git的方式也可以下载源码,但是下载v8源码不全,所以直接建议使用这个工具下载完整源码,而不需要使用git等方法下载部分残缺源码。 因为即使下载了源码也不全,后来还得继续下载完整版才行。
    v8源码地址(只下载depot工具,不下载源码即可)
    在这里插入图片描述
    下载完之后设置环境变量,比如我把deptot放在了F:\depot下。设置环境变量只需要按照我这一个方式设置即可。
    设置环境变量图
    之后还需要设置一个环境变量,直接用控制台然后输入

    DEPOT_TOOLS_WIN_TOOLCHAIN=0
    #再输入一行代码 来检查depot
    gclient
    

    1.3下载Windows SDK10

    直接在百度搜索windowskit10,然后安装,大概10分钟的时间。
    1.3步做完之后,就可以开始下载v8全套源码了!

    2.获取源码

    比如我想安装到c盘的v8文件夹

    #以下代码必须一行一行输入,不能直接全部复制
    cd c:\v8
    fetch v8
    

    这两行代码就需要执行相当长的时间,如果你网速好的话可能20分钟可以下完,网速差的话可能长达几小时时间。中途要是网络中断的话输入gclient sync -v来重新下载代码。

    当出现finished字样代表即将安装完成,我们再等待几分钟让源码完整安装完。之后就可以编译 了。

    最终会有类似下图的结果,代表全部代码安装完。
    目前来看,最新版代码安装好之后为2.48GB。建议直接使用最新版源码。
    在这里插入图片描述

    2.1可能出现的错误

    下图表示代理设置失败,检查第一步中的代理设置。
    在这里插入图片描述

    3.编译源码

    3.1 VS2019编译(1)

    在编译之前,首先要做的是把全部代码备份。因为可能会遇到奇怪的错误,备份代码以防万一。
    使用VS2019的控制台(而不是默认的控制台)输入以下代码(特别注意其中的\转义字符,十分重要):

    gn gen out.gn\x64.release_vs --sln=v8 --ide=vs2019 --args="is_debug=false target_cpu=\ “x64\ " is_component_build=true v8_static_library=false use_custom_libcxx=false use_custom_libcxx_for_host=false v8_use_external_startup_data=false is_clang=false v8_use_external_startup_data=false”

    gn gen out.gn\x64.release_vs --sln=v8 --ide=vs2019 --args="is_debug=false target_cpu=\"x64\" is_component_build=true v8_static_library=false use_custom_libcxx=false use_custom_libcxx_for_host=false v8_use_external_startup_data=false is_clang=false v8_use_external_startup_data=false"
    
    

    然后打开VS2019,直接BUILD gn_all这个项目(注意不是全部项目,只编译154个项目中的这个项目)
    但是即使是这个项目也要花费相当长的时间编译

    下图证明我是实际经过编译写的文章,这一步骤需要花费的时间更长。然而我在使用这个编译的时候仍然报错了。
    在这里插入图片描述

    3.2 VS2019编译(2)

    在VS2019的控制台输入以下代码gn gen --ide=vs out.gn\x64_solution
    然后编译gn_all

    我推荐使用这个方法,这个方法最简单,第一个方法相当于加上了参数,这里相当于把参数设置为默认,还是推荐在X64环境下编译,如果你是64位 系统的话推荐在64位环境编译。

    这里要注意到,如果你输入gn …等等代码的时候,提示找不到gn.exe,说明在获取源码出现问题,首先要做的是重新输入gclient sync -v重新获取源码,如果不行的话重新fetch v8!获得源码。以上的gn~命令必须正确运行才能使用VS2019进行编译。
    由于方法2和方法1几乎是同一个方法,所以如果报错的话报错的原因也基本类似。

    3.3另外一种编译的方式

    这个方式需要python老版本的支持。我还是推荐使用VS2019编译,能用新版本就用新版本。软件更新换代的时候要紧跟时代潮流。

    python tools\dev\v8gen.py x64.release
    ninja -C out.gn\x64.release
    

    最终编译后生成很多文件,有dll,有lib,其中关键的是v8.dll和v8.dll.lib文件生成。但是这种方法使用到了v8gen.py,而这个文件的函数容易出现错误。

    3.4直接编译方法(独创)★★

    本人由于其他原因在VS编译报错,网上探究了很长时间,最终发现了一个目前最好的编译方法(其实官方文档有写,但是比较隐晦,导致国内很多人不知道这个方法)。特别奇怪的是网上很多教程都是打开VS2019的,其实不用打开就能编译,调用Ninja即可。此方法为网上独创方法,其他博客写的方法虽有类似,但是执行过程中仍然会有困难,本人终于使用这个方法成功编译出了v8.dll
    直接在VS的控制台(注意不是系统自带的控制台)输入gn args out/x64.debug
    之后会弹出一个记事本文件。输入以下内容

        is_debug=true 
        target_cpu="x64"
        v8_enable_i18n_support=false 
        v8_use_external_startup_data=false 
        #use_custom_libcxx​=false 
        is_component_build=true
    

    紧接着直接使用ninja -C out/x64.debug v8
    就可以编译了(一定要注意执行过程中是否有出错,一般debug出错的概率更低一些)!如下图所示,能够成功编译
    在这里插入图片描述
    最终编译效果图(供参考,实际不一定一致)
    在这里插入图片描述

    4.补充代码

    4.1补充代码(1)

    这一模块所列代码有些包含了之前的代码,有些是经常在控制台需要反复输入的代码,有些是额外辅助代码。如果你之前步骤都没有错误到第三步已经可以使用了。第四步的代码是额外辅助用的。我把补充的代码也贴在这里,方便大家查询,同时自己也相当于做一个备份。

    #如果你想换老版本的话使用
    #必须进入到完整v8代码目录下输入git checkout才有效果
    git checkout 4.5.103.35
    #设置代理(可选代码)
    git config --global http.proxy http://127.0.0.1:8990
    git config --global https.proxy http://127.0.0.1:8990
    netsh winhttp set proxy 127.0.0.1:8990
    set HTTP_PROXY=http://127.0.0.1:8990
    set HTTPS_PROXY=http://127.0.0.1:8990
    #清除代理(可选代码)
    git config --global --unset http.proxy
    git config --global --unset https.proxy
    netsh winhttp reset proxy
    set HTTP_PROXY=
    set HTTPS_PROXY=
    #控制台设置变量
    SET DEPOT_TOOLS_WIN_TOOLCHAIN=0
    #开始下载
    fetch v8
    #下面是切换版本 如果不切换版本就是最新版
    cd v8
    git checkout 8.8-lkgr
    gclient sync -v
    #编译1
    gn gen --ide=vs out.gn\x64_solution
    #编译2
    gn gen out.gn\x64.release_vs --sln=v8 --ide=vs2019 --args="is_debug=false target_cpu=\"x64\" is_component_build=true v8_static_library=false use_custom_libcxx=false use_custom_libcxx_for_host=false v8_use_external_startup_data=false is_clang=false v8_use_external_startup_data=false"
    

    4.2补充代码(2)

    #### fetch
    cd v8-
    git checkout branch-heads/8.3 #(失效)
    git pull branch-heads/8.3 #(失效)
    gclient sync -D
    #### debug
       gn args out/x64.debug 
     
        is_debug=true 
        target_cpu="x64"
        v8_enable_i18n_support=false 
        v8_use_external_startup_data=false 
        #use_custom_libcxx​=false 
        is_component_build=true
    
       ninja -C out/x64.debug v8
    #### clean
       ninja -C out/x64.debug -t clean
     
    #### release
    gn args out/x64.release
           is_debug=false
           target_cpu="x64"
           v8_enable_i18n_support=false 
           v8_use_external_startup_data=false 
           use_custom_libcxx=false 
           is_component_build=true
     
     ninja -C out/x64.release v8
    #### clean
        ninja -C out/x64.release -t clean
    

    5.使用V8引擎

    直接把代码复制进去,然后需要注意的是,包含目录、附加库目录、附加依赖项三个。具体一些:
    A、添加工程的头文件目录:工程—属性—配置属性—C/C+±–常规—附加包含目录:加上头文件存放目录。
    B、添加文件引用的lib静态库路径:工程—属性—配置属性—链接器—常规—附加库目录:加上lib文件存放目录。
    C 然后添加工程引用的lib文件名:工程—属性—配置属性—链接器—输入—附加依赖项:加上lib文件名

    // Copyright 2012 the V8 project authors. All rights reserved.
    // Redistribution and use in source and binary forms, with or without
    // modification, are permitted provided that the following conditions are
    // met:
    //
    //     * Redistributions of source code must retain the above copyright
    //       notice, this list of conditions and the following disclaimer.
    //     * Redistributions in binary form must reproduce the above
    //       copyright notice, this list of conditions and the following
    //       disclaimer in the documentation and/or other materials provided
    //       with the distribution.
    //     * Neither the name of Google Inc. nor the names of its
    //       contributors may be used to endorse or promote products derived
    //       from this software without specific prior written permission.
    //
    // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
    // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
    // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
    // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
    // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
    // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
    // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
    // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
    // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
    // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
    #include <v8.h>
    
    #include <libplatform/libplatform.h>
    
    #include <assert.h>
    #include <fcntl.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    /**
     * This sample program shows how to implement a simple javascript shell
     * based on V8.  This includes initializing V8 with command line options,
     * creating global functions, compiling and executing strings.
     *
     * For a more sophisticated shell, consider using the debug shell D8.
     */
    
    
    int main(int argc, char* argv[]) {
        // Initialize V8.
        v8::V8::InitializeICUDefaultLocation(argv[0]);
        v8::V8::InitializeExternalStartupData(argv[0]);
        std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
        //std::unique_ptr<v8::Platform> platform;
        v8::V8::InitializePlatform(platform.get());
        v8::V8::Initialize();
    
        // Create a new Isolate and make it the current one.
        v8::Isolate::CreateParams create_params;
        create_params.array_buffer_allocator =
            v8::ArrayBuffer::Allocator::NewDefaultAllocator();
        v8::Isolate* isolate = v8::Isolate::New(create_params);
        {
            v8::Isolate::Scope isolate_scope(isolate);
    
            // Create a stack-allocated handle scope.
            v8::HandleScope handle_scope(isolate);
    
            // Create a new context.
            v8::Local<v8::Context> context = v8::Context::New(isolate);
    
            // Enter the context for compiling and running the hello world script.
            v8::Context::Scope context_scope(context);
    
            {
                // Create a string containing the JavaScript source code.
                v8::Local<v8::String> source =
                    v8::String::NewFromUtf8Literal(isolate, "'Hello' + ', World!'");
    
                // Compile the source code.
                v8::Local<v8::Script> script =
                    v8::Script::Compile(context, source).ToLocalChecked();
    
                // Run the script to get the result.
                v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
    
                // Convert the result to an UTF8 string and print it.
                v8::String::Utf8Value utf8(isolate, result);
                printf("%s\n", *utf8);
            }
    
            {
                // Use the JavaScript API to generate a WebAssembly module.
                //
                // |bytes| contains the binary format for the following module:
                //
                //     (func (export "add") (param i32 i32) (result i32)
                //       get_local 0
                //       get_local 1
                //       i32.add)
                //
                const char csource[] = R"(
            let bytes = new Uint8Array([
              0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x07, 0x01,
              0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x03, 0x02, 0x01, 0x00, 0x07,
              0x07, 0x01, 0x03, 0x61, 0x64, 0x64, 0x00, 0x00, 0x0a, 0x09, 0x01,
              0x07, 0x00, 0x20, 0x00, 0x20, 0x01, 0x6a, 0x0b
            ]);
            let module = new WebAssembly.Module(bytes);
            let instance = new WebAssembly.Instance(module);
            instance.exports.add(3, 4);
          )";
    
                // Create a string containing the JavaScript source code.
                v8::Local<v8::String> source =
                    v8::String::NewFromUtf8Literal(isolate, csource);
    
                // Compile the source code.
                v8::Local<v8::Script> script =
                    v8::Script::Compile(context, source).ToLocalChecked();
    
                // Run the script to get the result.
                v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
    
                // Convert the result to a uint32 and print it.
                uint32_t number = result->Uint32Value(context).ToChecked();
                printf("3 + 4 = %u\n", number);
            }
        }
    
        // Dispose the isolate and tear down V8.
        isolate->Dispose();
        v8::V8::Dispose();
        v8::V8::ShutdownPlatform();
        delete create_params.array_buffer_allocator;
        return 0;
    }
    

    6.心得总结

    这个基本上是目前我调试环境变量最复杂的一个引擎(主要是方法不对绕弯路),但我还是希望用简单的方式来指导更多人学会使用这个引擎,v8真的很强大,仔细阅读源码也能收获很多(代码写的相当规范,几百人Google团队开发10年代码不是随便的人就能实现的)
    但是使用v8比编译的难度更大,国内几乎没多少人在用,讨论交流比较困难,要想学习还需要多多涉猎,特别是一些底层的知识原理。
    另外,本人是直接使用的最新版本进行编译的,但是这样做其实不好,最新版本bug可能比较多(Google更新速度太快,真的有可能出BUG),不妨换版本,换成低版本的方法,说不定你在低版本就可以使用了。

    git checkout 8.4-lkgr
    gclient sync -D
    

    其中的-D命令为删除多余文件,释放空间用。
    PS:本篇文章持续更新,发现新问题新方法会补充在文章之中。

    展开全文
  • 第二篇文章将深入谷歌 V8 的JavaScript引擎的内部。想阅读更多优质文章请猛戳GitHub博客,一年百来篇优质文章等着你!概述JavaScript引擎是执行 JavaScript 代码的程序或解释器。JavaScript引擎可以实现为标准解释器...
  • V8引擎静态库分为x86版本和x64版本 V8引擎静态库下载地址 编译工具:VS2019 v8开头的即为V8引擎静态库(附送其它开源静态库libuv.lib、zlib.lib等_) 如何调用V8引擎 1、下载v8静态库到本地 2、配置开发环境 将...
  • 问题抛出:JavaScript代码在浏览器内是如何被执行的呢?(下一篇解答) 本文做知识的铺垫,有助于对原理的理解。 浏览器输入url后的大致...浏览器的内核又叫排版引擎,浏览器引擎,渲染引擎 浏览器的渲染过程 浏览.
  • 一、编译v8引擎

    2021-06-23 22:32:49
    What is V8? V8 is Google’s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others. 1. 源码仓库 V8’s Git repository is ...
  • Javascript引擎线程: Javascript引擎,也可以称为JS内核,主要负责处理Javascript脚本程序,例如V8引擎。Javascript引擎线程理所当然是负责解析Javascript脚本,运行代码。 Javascript是单线程的: Javascript是单...
  • V8引擎 Android库构建

    2021-06-02 17:08:04
    强烈推荐大家在linux系统下构建v8引擎,如果您喜欢折腾,非要使用MAC,可参考这篇文章,可以帮您更快速上手MAC 下 V8 Android编译指墙。Checking out the V8 source code方案一在国内需要做以下配置是关键,其余部分...
  • 及时编译—之前的JS执行引擎需要先将源码转成字节码然后才能去执行,而对于V8来说可以直接将源码翻译成可执行的机器码 内存空间设置了数值,对于64位操作系统来说不超过1.5G,对于32位操作系统来说不超过800M—V8是...
  • 该对象包含四个字段,含义如下: clipboard.png rss(resident set size):所有内存占用,包括指令区和堆栈 heapTotal:V8引擎可以分配的最大堆内存,包含下面的 heapUsed heapUsed:V8引擎已经分配使用的堆内存 ...
  • V8 引擎简介 Google V8 引擎使用 C++ 代码编写,实现了 ECMAScript 规范的第五版 ,可以运行在所有的主流操作系统中,甚至可以运行在移动终端 ( 基于 ARM 的处理器,如 HTC G7 等 )。V8 最早被开发用以嵌入到 Google...
  • 深入V8引擎-AST(5)

    2021-03-08 07:49:11
    /** * 参数是一个匿名函数*/AdvanceUntil([this](uc32 c0) {//Unicode大于127的特殊字符 if (V8_UNLIKELY(static_cast(c0) >kMaxAscii)) {/** * 检测是否是换行符 * rn以及n*/ if(V8_UNLIKELY(unibrow::...
  • 本篇主要是从V8引擎的垃圾回收机制入手,讲解一下在JavaScript代码执行的整个生命周期中V8引擎是采取怎样的垃圾回收策略来减少内存占比的。对这方面有一定的了解之后,能增强我们在写代码过程中对减少内存占用,避免...
  • 在这一篇文章中我们将来学习如何让 V8引擎 优化我们的代码,之后将会学习性能优化剩余的琐碎点,因为性能优化这个领域所涉及的内容都很碎片化。在学习如何性能优化之前,我们先来了解下如何测试性能问题,毕竟是先有...
  • 最近学习了极客时间上李兵大佬的谷歌V8引擎课程,总结了一下,在公司内部小组分享了一波,在此也分享一下,原理图直接用的专栏的图,由于时间有限,总结略显粗糙 注:解释执行、编译执行各有优缺点: 解释...
  • 了解V8引擎如何运行JS

    2021-03-23 15:43:14
    今天看了一个视频,关于V8引擎是如何运行JS的。我将视频中主要的知识点记下来,一来加深记忆,二来方便复习 什么是V8 V8是使用C++编写的Google开源高性能JavaScript和WebAssembly引擎。V8第一个版本随着第一个版本...
  • 本文将为大家介绍,V8引擎如何执行JavaScript代码。 看完本文可以掌握,以下几个方面: JavaScript的执行原理; 浏览器内核的真实结构; 浏览器渲染引擎的工作过程; V8引擎的工作原理; 浏览器和Node.js架构...
  • V8引擎内存回收机制

    2021-10-12 11:56:00
    常见的GC算法原理与V8引擎内存回收机制引用计数算法标记清除标记整理V8引擎的内存回收 引用计数算法 引用计数算法介绍 核心思想就是内部通过一个引用计数器来维护当前对象的引用次数,从而判断该对象的引用数值是否...
  • V8PyWrite Python APIs, then call them from JavaScript using the V8 engine.>>> from v8py import Context>>> context = Context()>>> def print_hello():... print 'Hello, worl...
  • 浏览器解析过程 ... ...遇见 style/link 标记调用相应解析器处理CSS标记,并构建出CSS样式树 遇见 script 标记 调用javascript引擎 处理script标记、绑定事件、修改DOM 树/CSS树...浏览器引擎分为两部分,渲染引擎和js引擎
  • 【JS】V8 引擎原理

    千次阅读 2021-12-08 21:20:41
    文章目录认识JS引擎V8引擎原理在这里插入图片描述 **流程图分析:**V8引擎架构 认识JS引擎 为什么需要 JS 引擎? 1. `高级编程语言`都有转化成`最终的机械指令`来执行的 2. 我们平时编写的js,无论你交给`浏览器或...
  • 在做v8编译之前,请做好踩坑准备,这篇帖子是为了让其他同学遇到我这种类似的问题的时候有一个参考,不一定就一定能解决掉你的问题,毕竟在操作的时候各种因素都会影响最后的结果。 本人在编译时遇到的各种问题,也...
  • JavaScript引擎:将 JavaScript 代码翻译成 CPU 指令来执行。 浏览器内核:以WebKit为例 WebCore:负责HTML解析、布局、渲染等等相关的工作; JavaScriptCore:解析、执行JavaScript代码; ...
  • 三、VS2019编译v8引擎

    2021-07-01 21:45:17
    接着上一篇实践发现的问题《C++使用v8引擎执行JavaScript_永远的魔术1号-CSDN博客》,我在网上查找了很多资料,原因都是说,由于v8默认使用了clang编译,而clang使用的是libc++作为C++标准库,而demo使用的环境则是...
  • 他指出,该漏洞位于 V8 JavaScript 引擎中。该引擎是负责处理和执行浏览器内 JavaScript 代码的开源组件。 V8 是所有基于 Chromium 浏览器的标准 JavaScript 引擎,该项目主要由 Chromium 项目的谷歌开发人员负责...
  • 基于这个假说,v8 引擎将内存分为了新生代和老生代。 新创建的对象或者只经历过一次的垃圾回收的对象被称为新生代。经历过多次垃圾回收的对象被称为老生代。 新生代被分为 From 和 To 两个空间,To 一般是闲置的。当...
  • 说下 V8 引擎解释执行 js 代码的大致流程 一、前置知识 1.1编程语言是如何运行的? 众所周知,我们通过编程语言完成的程序是通过处理器运行的。但是处理器不能直接理解...
  • 参考《Getting started with embedding V8 · V8》 Accessing static global variables 首先,封装对全局变量的Set和Get方法: void GlobalValueGetter( Local<String> property, const ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 33,584
精华内容 13,433
关键字:

v8的引擎