如何将二进制机器码转换成汇编指令_v8是如何执行一段JavaScript代码的?

1、什么是v8? V8是JavaScript虚拟机的一种,可以理解为一个翻译程序,将人类能够理解的编程语言翻译成机器能够理解的机器语言。(用来执行js代码) a640fd787003a19b7e71c95bff0966da.png 市面上有多种JavaScript引擎:spiderMonkey、V8、JavaScriptCore 在v8之前,所有JavaScript虚拟机都是采用解释执行的方式。V8引入了即时编译,混合编译执行和解释执行两种手段,给JavaScript的执行速度带来极大的提升。 V8还引入了惰性编译、内联缓存、隐藏类等机制。进一步优化的JavaScript的编译执行速度。 22eda966f7e5f8595812422176b2fabb.png 2、高级语言为什么先编译后执行(CPU怎么执行机器代码的)? 可以把CPU看成是非常小的运算机器。我们可以通过二进制指令和CPU进行通信。比如给CPU发出 1000100111011000 的二进制指令,这条指令的意思是将寄存器中的数据移到另一个寄存器。 CPU 只能识别二进制指令 所以需要一个汇编编译器,将汇编代码转为机器代码。 002979d56893edf9e4195e90581f7615.png 但是汇编语言依然是复杂繁琐的。 首先,不同的CPU有着不同的指令集。所以你需要为每种架构的CPU编写特定的汇编代码。 c5021801da2aca74cedfd375e53e033c.png 在编写汇编代码时,我们需要了解和处理器架构相关的硬件知识。比如需要寄存器、内存、操作CPU等。 因此需要一种屏蔽计算机架构细节的语言。能适应多种CPU的架构语言。比如C、C++、JavaScript、Java、C#、python 和汇编语言一样,处理器不能识别高级语言。通过两种方式来执行这些代码。 第一种是解释执行。 需要将输入的源代码通过解析器编译成中间代码,之后直接使用解释器解释执行中间代码,然后直接输出结果。 9c9573aa01628b5edc7223b37e0be08e.png 第二种是编译执行。 也需要将源代码转为中间代码,然后编译器再将中间代码编译成机器代码。通常编译成的机器代码是二进制形式存在的。需要执行这段程序直接执行二进制文件就可以了。 还可以使用虚拟机将编译后的机器代码保存在内存中,直接执行内存中的二进制代码。 8420f3d039ae17e66a9d2b9f6ae1df8b.png 3、v8是如何执行一段JavaScript代码的? 其主要核心流程是编译和执行两步。首先将JS代码转换为低级中间代码或者机器能够理解的代码,然后再执行并输出结果。 可以把V8看做是虚拟机,模拟实际计算机的各种功能来实现代码的执行。 比如:模拟实际计算机的CPU、堆栈、寄存器等。 V8 采用混合编译执行和解释执行两种手段。称为JIT(just in time) 因为这两种各有各自的优缺点。解释执行的启动速度快,但执行速度慢。而编译执行的启动速度慢,执行速度快。 22eda966f7e5f8595812422176b2fabb.png 首先,在v8启动执行js之前,需要准备执行时的准备环境,包括堆空间、栈空间、执行上下文、消息循环系统、内置函数等。 这 些内容都需要在执行JavaScript过程中需要的。 比如: JavaScript全局执行上下文就包含了执行过程中的执全局信息,比如内置函数、全局变量等。 全局作用域就包含了一些全局变量,在执行过程中数据都需要保存在内存中。 V8 采用了堆和栈的内存管理方式,所以v8还需要初始化内存中的堆和栈结构。 另外还需要初始化消息循环系统,它如同v8的心脏,不断接受消息,并处理。 基础环境准备好后,接下来就可以向v8提交需要执行的代码了。 结构化源代码,生成抽象语法树(AST),即便于v8理解的结构。同时生成相关的作用域,作用域中存放变量。 接下来生成字节码。是介于机器码和AST的中间代码。 解释器可以执行解释执行字节码。 在解释执行的过程中,如果 发现某一段代码被重复多次执行,那么就会这段代码标记为热点代码。 V8会将这段字节码丢给优化编译器,优化编译器会把字节码编译为二进制代码,然后再对二进制代码进行优化,优化后的二进制代码执行效率会大幅提升。 但是和静态语言不同,JavaScript是动态语言,对象的结构和属性是可以在运行期间任意修改的,而优化后的代码只针对某种固定结构,一旦在执行过程中,对象的结构被动态修改了,那么优化后的代码势必变成无效的代码。 这时候就要优化编译器执行反优化操作,下次执行就退回解释器解释执行。 总结: 1 、初始化基础环境; 2、解析源码生成 AST 和作用域; 3、依据 AST 和作用域生成字节码; 4、解释执行字节码;监听热点代码; 5、优化热点代码为二进制的机器代码; 6、反优化生成的二进制机器代码。 4、v8为什么又要引入字节码? 机器代码缓存 0908ef2a77009fcf52872484cf2f7365.png 从图中可以看出,编译所消耗的时间和执行所消耗的时间是差不多的,试想一下,如果在浏览器中再次打开相同的页面,当页面中的 JavaScript 文件没有被修改,那么再次编译之后的二进制代码也会保持不变, 这意味着编译这一步白白浪费了 CPU 资源,因为之前已经编译过一次了。 这就是 Chrome 浏览器引入二进制代码缓存的原因,通过把二进制代码保存在内存中来消除冗余的编译,重用它们完成后续的调用,这样就省去了再次编译的时间。 V8 使用两种代码缓存策略来缓存生成的代码。
  • 首先,是 V8 第一次执行一段代码时,会编译源 JavaScript 代码,并将编译后的二进制代码缓存在内存中,我们把这种方式称为内存缓存(in-memory cache)。
  • 将代码缓存到硬盘上,这样即便关闭了浏览器,下次重新打开浏览器再次执行相同代码时,也可以直接重复使用编译好的二进制代码。
5d5b4c929e40432c7e1f4f413a70deaf.png 字节码降低内存占用 V8 在执行 JavaScript 代码的过程中,会将 JavaScript 代码转换为未经优化的二进制代码,你可以对照下图中的 JavaScript 代码和二进制代码的: b68858699dbd7f2157d51819997041aa.png 二进制代码所占用的内存空间是 JavaScript 代码的几千倍, V8 团队为了提升 V8 的启动速度,采用了 惰性编译 ,其实惰性编译除了能提升 JavaScript 启动速度,还可以解决部分内存占用的问题。 c108df065b45f8f3e4f8044c02de9a93.png 根据惰性编译的原则,当 V8 首次执行上面这段代码的过程中,开始只是编译最外层的代码,那些函数内部的代码,如下图中的黄色的部分,会推迟到第一次调用时再编译。 为了解决缓存的二进制机器代码占用过多内存的问题,早期的 Chrome 并没有缓存函数内部的二进制代码,只是缓存了顶层次的二进制代码,比如上图中红色的区域。 如果浏览器只缓存顶层代码,那么闭包模块中的代码将无法被缓存,而对于高度工程化的模块来说,这种模块式的处理方式到处都是,这就导致了一些关键代码没有办法被缓存。 所以采取只缓存顶层代码的方式是不完美的,V8 团队对早期的 V8 架构进行了非常大的重构,具体地讲,抛弃之前的基线编译器和优化编译器,引入了字节码、解释器和新的优化编译器。 131f349e8a5bcff9eded2cd92ce0c6e6.png 从图中可以看出,字节码虽然占用的空间比原始的 JavaScript 多,但是相较于机器代码,字节码还是小了太多。 有了字节码,无论是解释器的解释执行,还是优化编译器的编译执行,都可以直接针对字节来进行操作。由于字节码占用的空间远小于二进制代码,所以浏览器就可以实现缓存所有的字节码,而不是仅仅缓存顶层的字节码。 虽然采用字节码在执行速度上稍慢于机器代码,但是整体上权衡利弊,采用字节码也许是最优解。 字节码如何提升代码启动速度? 87632b6461a2ebacb1ba2193b9e066d2.png 从图中可以看出,生成机器代码比生成字节码需要花费更久的时间,但是直接执行机器代码却比解释执行字节码要更高效,所以在快速启动 JavaScript 代码与花费更多时间获得最优运行性能的代码之间,我们需要找到一个平衡点。 解释器可以快速生成字节码,但字节码通常效率不高。 字节码如何降低代码的复杂度? 8ec5bde10e14a35a6ca1d6cb137e8042.png 这意味着基线编译器和优化编译器要针对不同的体系的 CPU 编写不同的代码,这会大大增加代码量。 引入了字节码,就可以统一将字节码转换为不同平台的二进制代码, 你可以对比下执行流程: ae5e2705573a65126d2db502c815ba04.png 因为字节码的执行过程和 CPU 执行二进制代码的过程类似,相似的执行流程,那么将字节码转换为不同架构的二进制代码的工作量也会大大降低,这就降低了转换底层代码的工作量。 总结: 不过随着移动设备的普及,V8 团队逐渐发现将 JavaScript 源码直接编译成二进制代码存在两个致命的问题:
  • 时间问题:编译时间过久,影响代码启动速度;
  • 空间问题:缓存编译后的二进制代码占用更多的内存。
这两个问题无疑会阻碍 V8 在移动设备上的普及,于是 V8 团队大规模重构代码,引入了中间的字节码。字节码的优势有如下三点:
  • 解决启动问题:生成字节码的时间很短;
  • 解决空间问题:字节码占用内存不多,缓存字节码会大大降低内存的使用;
  • 代码架构清晰:采用字节码,可以简化程序的复杂度,使得 V8 移植到不同的 CPU 架构平台更加容易。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值