​《WebKit 技术内幕》学习之九(3): JavaScript引擎

news2024/5/17 20:11:40

3 JavaScriptCore引擎

3.1 原理

        JavaScriptCore引擎是WebKit中的默认JavaScript引擎,也是苹果在开源WebKit项目之后,开源的另外一个重要的项目。同其他很多引擎一样,在刚开始的时候它的主要部分是一个基于抽象语法树的解释器,这使得它的性能实在太差。

        从2008年开始,JavaScriptCore引擎开始一个新的优化工作,重新实现了编译器和字节码解释器,这就是SquirrelFish。该工作对于引擎的性能优化做了比较大的改进。随后,苹果内部代号为“Nitro”的JavaScript引擎也是基于JavaScriptCore项目的,它的性能还是非常出色的,鉴于其是内部项目,所以具体还有什么特别的处理就不得而知了。在这之后,开发者们又将内嵌缓存、基于正则表达式的JIT和简单的JIT引入到JavaScriptCore中。然后,又陆续加入了字节码解释器。可以看出,JavaScriptCore引擎也在不断地高速发展中。

3.2 架构和模块

3.2.1 代码结构

        根据JavaScriptCore项目的代码结构和之前介绍的引擎的工作过程,读者大概可以猜测出代码结构中到底有哪些主要模块和基本的工作了,因为该结构划分的粒度比V8项目细致多了,还是比较容易理解的,如图9-20所示的代码结构目录。

        

                                图9-20 JavaScriptCore代码结构

        从代码目录中,我们可以猜测并理解它的演进过程:首先是词法和语法分析,然后使用底层解释器来解释那些字节码。之后,通过简单的JIT编译器将它们转化成本地代码。还没结束,最后就是引入DFG JIT编译器。

        这些目录直接跟即将介绍的各个技术有很好的对应关系,读者先有个大致的理解,这样对后面的介绍大有帮助,感兴趣的读者还可以去查找源码来有个基本的认识。

3.2.2 数据表示

        JavaScriptCore引擎同样使用句柄来表示数据,对于简单类型的数据则直接包含在句柄中,而对于对象来说,则使用指针来指向数据在堆中的位置。同V8引擎不同的是,在32位和64位机器上,句柄都是使用64位来表示的,图9-21分别描述了两种平台上各种类型的表示和识别方式。

                                图9-21 句柄的定义和各种类型的表示方式

        首先在32位平台上,每个句柄都是使用两个32位数据来表示。对于整数、布尔和指针而言,前面32位用来标记它们,后面32位用来表示这些数据。对于双浮点,前32位在区间FFFFFFF8~00000000都是用来表示浮点类型,可能稍微比原来的双浮点表示范围小一些,但是,这个范围已经足够使用了。同样在64位机器上,因为标记指针需要64位,只好使用前面16位(0000),而后面的48位用来表示地址,读者可能觉得这样就没有64位表示指针,但是实际上48位已经足够。

        同V8引擎相比,JavaScriptCore引擎因为在32位上使用64位来表示句柄,所以除了小整数之外,对于浮点类型同样可以不需要访问堆中的数据,当然,缺点就是每个句柄都需要2倍的内存空间。

3.2.3 模块

        同V8一样的是,JavaScriptCore引擎在开源之后也引入了众多新技术。不过,JavaScriptCore引擎与V8相比还是有很多不同之处的,最典型的就是它使用了字节码的中间表示,并加入了多层JIT编译器帮助改善性能,不停地优化编译之后的本地代码。当然JavaScriptCore在不停地演进的过程中,目前的实现跟之前的实现差别非常大,所以这里介绍的是基于目前的结构的,在未来,可能还会有很多其他的变化,让我们拭目以待。

        第一,不同于V8引擎,JavaScriptCore引擎不是从抽象语法树生成本地代码,而是生成平台无关的字节码,如图9-22所示。JavaScriptCore引擎自己定义了一套字节码规范,该字节码与平台无关,而且有了该字节码,JavaScriptCore就可以基于其进行很多在抽象语法树之上不能或者很难做到的优化。读者需要记住的是,不同于V8,在这之后,因为有了字节码,所以JavaScriptCore就不再需要JavaScript源代码,而V8使用Crankshaft编译器进行进一步优化,则需要继续从JavaScript源代码重新开始。

                                图9-22 JavaScriptCore中从源代码到字节码

        第二,在字节码之后,JavaScriptCore依然包含了字节码解释器,这点也类似于Java虚拟机中的解释器,它们都能够解释字节码然后生成结果。而不同于Java虚拟机中的解释器的是,JavaScriptCore是基于虚拟寄存器(Virtual Register)的虚拟机,而Java是基于栈式(Stack)的虚拟机。这一解释器很有必要,因为一些JavaScript代码不需要经过很强的优化,只需要直接执行即可,复杂的处理可能带来额外开销反而抵消了优化带来的全部好处,如图9-23所示。同时,在字节码执行期间,信息收集器会收集热点函数,以方便之后的JIT编译器做之后的优化处理。图中的信息收集器1之所以加上“1”,是为了区别JavaScriptCore中包含的各种各样的信息收集器。

                图9-23 JavaScriptCore从字节码到解释器和信息收集器

         第三,JavaScriptCore引擎在获悉热点函数后,需要对它们进行优化,就会使用到简单(Baseline)JIT编译器,该编译器根据信息收集器1中的信息,将对应函数的字节码翻译成本地代码,不仅因为时间问题,而且并不是所有代码都合适做深层次的优化,所以这里没有做特别多的优化,而是直接做转换。图9-24描述了这一过程。在实行这些本地代码的时候,会有信息收集器2来收集代码并作做一步的优化。

                图9-24 JavaScriptCore的简单JIT编译器

        第四,如果你认为只需要JIT编译器就够了,那就错了,简单的JIT编译器并不能满足性能的要求,特别是对V8的Crankshaft编译器来说,性能差距就显现出来了。为了提高性能,JavaScriptCore中又引入了DFG(Data-Flow Graph)JIT编译器,该编译器是在字节码基础上,生成基于SSA(Static Single Assignment)的中间表示(IR)。当然具体哪些字节码需要重新生成优化的本地代码,就依赖之前的信息收集器2,如图9-25所示。优化后的本地代码相比之前的代码,对于性能有很好的提升。

                                图9-25 JavaScriptCore的DFG JIT编译器

        第五,要是你认为这样就足够了,那就更错了。在笔者介绍JavaScriptCore的时候,该项目依然在进行一项更为大胆的工作,就是将LLVM技术引入到JavaScriptCore。那么LLVM是什么呢?LLVM是一个由苹果公司发起的开源项目,其开发和灵活的架构受到越来越多人的关注。

        LLVM是一个编译器,能够将多个不同的前端语言转化成不同的后端本地代码,图9-26描述了LLVM的基本结构,该编译器在前端和后端都能做优化,这些优化都是可配置的,所以非常灵活。同时,随着该项目越来越成功,加入的优化也越来越多。JavaScriptCore希望将LLVM编译器的中间表示引入其中,这样将很容易将这些优化使用在该引擎中,图9-27描述了这一过程。

                                        图9-26 LLVM基本结构

                                        图9-27 使用LLVM技术的JIT编译器

        这一过程是基于DFG JIT中间表示开始的,为了节省时间,使用了并行编译算法。之后,生成LLVM的中间表示,这样就可以使用LLVM中间表示之后的众多优化,而且可以按需配置它们。这一过程仅仅对于那些最热点的函数使用,因为其层次太多,消耗的时间更多,所以慎用。这一技术目前还在开发中,未来效果如何还未可知,不过相信对于某些特定的例子会有不少好处。

        为什么不直接使用优化性能最好的编译器呢?原因是优化越好通常需要的分析和生成代码的时间就越长。读者回忆之前介绍的应用场景就会发现,如果用户使用的是利用C/C++编译的代码,那么编译时间长一点问题不大,因为是开发者在编译他们。而对于JavaScript来说,编译时间越长,对用户来说同样,等待的时间更长,效果可能也未必会好。这就是一把双刃剑,所以该方法只限定在特定的范围内使用。

3.4 内存管理

        在JavaScriptCore中,内存管理和垃圾回收机制也随着其他技术的改变而发生着很大的变化。对于垃圾回收机制来说,最重大的改变就是像V8一样,引入了分代垃圾回收机制。所以,堆也会被分成几个分代。这样,当进行垃圾回收的时候,就不需要对所有对象进行标记。分代技术前面也讨论过了,而且很早就在其他虚拟机中使用,如Java虚拟机,它们思想都是类似的,这里不再赘述。

         在V8中使用Zone来一次性释放内存,JavaScriptCore中也有类似的机制,那就是JSGlobalData,这里也不再过多的描述。

3.5 绑定

        JavaScriptCore同样能够提供绑定机制,目前渲染引擎同样是通过该机制访问DOM的操作函数,这点跟V8非常像。本质上,它们都是提供额外的JavaScript接口来扩展JavaScript引擎的能力。同样,我们将在下一章做详细介绍。

3.6 比较JavaScriptCore和V8

        由于JavaScriptCore一直是Webkit的默认JavaScript引擎,所以被广泛应用。但是,随着Google发布Chrome的同时加上V8引擎,而且V8自出现后就是以性能作为目标,引入了众多新颖的技术,确实极大地推动了整个业界的JavaScript引擎性能的快速发展。但是,如果想用一句话说明V8和JavaScriptCore的优劣,这是很困难的。在很多领域,V8扮演着冲锋者的角色,但是JavaScriptCore依旧不断改进自己的技术和实现,同时在某些方面,因为使用了一些V8没有的东西,如字节码反而在某些情况下较容易优化。当然,这也不是绝对的。

        关于各个技术细节,例如内部代码表示、解释器、JIT、句柄数据表示等方面,我们在前面都一一做了介绍,读者可以回忆一番。我们前面已经介绍了以上两个引擎的很多特点和好处,笔者还希望留一些想象的空间,让读者自己体会上面这些技术细节带来的潜在优势和缺点,以及潜在的发展方向。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1403584.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据库原理】(38)数据仓库

数据仓库(Data Warehouse, DW)是为了满足企业决策分析需求而设计的数据环境,它与传统数据库有明显的不同。 一.数据库仓库概述 定义: 数据仓库是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持企业管理和…

成都爱尔胡建斌院长提醒视网膜脱离到底有多危险?!

视网膜脱离是视网膜神经上皮层与色素上皮层的分离。 视网膜脱离之危险,在与视网膜脱离后外层视网膜得不到脉络膜的血液供应,如不及时复位,视网膜感光细胞会发生凋亡,视力就不易恢复。 就症状来说,患者发病初期眼前多有…

0122-1-JavaScript高级程序设计11-27章

前言 通过阅读这本书写下的一些笔记 《JavaScript高级程序设计》 第11章——期约与异步函数 11.2 期约(promise):是对 尚不存在结果 的一个替身。 /*** 期约与异步函数* 什么是Promise?* (1)从语法上来说:Promis…

np.argsort排序问题(关于位次)-含GitHub上在numpy项目下提问的回复-总结可行方案

np.argsort 与获取位相关问题 位次: 数组中的数据在其排序之后的另一个数组中的位置 [1,0,2,3] 中 0的位次是1 1的位次是2 2的位次是3 3的位次是4 这里先直接给出结论,np.argsort()返回的索引排序与实际位次在确实在某些情况下会出现一致,但后来numpy的开…

HubSpot SEO功能好用吗?

HubSpot对于SEO(搜索引擎优化)提供了全面的工具和功能,帮助用户优化其网站以在搜索引擎中取得更好的排名。以下是关于HubSpot SEO的一些关键方面: 内容优化: HubSpot的内容工具允许用户创建并优化吸引人的内容。通过关…

pikachu验证码绕过第三关攻略

打开pikachu靶场第三关: 挂上代理,随便输入账户密码: 返回bp。进行放包发现显示token错误。 每一次登录的返回包会带有token相关数据用于下一次的登录认证: 进行替换token值: 替换完成开始进行检点的爆破:…

s3fs挂载minio集群到本地目录

转载说明:如果您喜欢这篇文章并打算转载它,请私信作者取得授权。感谢您喜爱本文,请文明转载,谢谢。 1. 前言 MinIO 是一款高性能的对象存储,与 Amazon S3 云存储服务兼容,并且号称是世界上最快的对象存储服…

LLM RAG 多种方式装载LLM的实践

一、大模型系统中检索增强生成(RAG)的意义 当前大模型在处理特定领域或者高度专业化的查询时表现出知识缺失,当所需信息超出模型训练数据范围或需要最新数据时,大模型可能无法提供准确答案。基于行业SOP、行业标准、互联网实时信…

中间件-缓存、索引、日志

文章目录 缓存中间件本地缓存中间件分布式缓存中间件全文索引中间件分布式日志中间件小结 缓存中间件 缓存是性能优化的一大利器 我们先一起来看一个用户中心查询用户信息的基本流程 这时候,如果查找用户信息这个 API 的调用频率增加,并且在整个业务流…

强化学习(四)动态规划——1

动态规划算法(DP):在马尔可夫决策过程(MDP)的完美环境模型下计算最优策略。但其在强化学习中实用性有限,其一是它是基于环境模型已知;其二是它的计算成本很大。但它在理论伤仍然很重要&#xff…

Vscode 顶部Menu(菜单)栏消失如何恢复

Vscode 顶部Menu(菜单)栏消失如何恢复? 首先按一下 Alt按键,看一下是否恢复了菜单栏如果恢复了想了解更进一步的设置,或是没能恢复菜单栏,可以看后续。 1.首先点击左下角 齿轮,打开settings; 或者 直接 ctrl 逗号 …

如何本地搭建Splunk Enterprise数据平台并实现任意浏览器公网访问

文章目录 前言1. 搭建Splunk Enterprise2. windows 安装 cpolar3. 创建Splunk Enterprise公网访问地址4. 远程访问Splunk Enterprise服务5. 固定远程地址 前言 本文主要介绍如何简单几步,结合cpolar内网穿透工具实现随时随地在任意浏览器,远程访问在本地…

java SSM项目预算生成管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM项目预算生成管理系统是一套完善的web设计系统(系统采用SSM框架进行设计开发,springspringMVCmybatis),对理解JSP java编程开发语言有帮助,系统具有完整的 源代码和数据库,系统主…

《WebKit 技术内幕》学习之十(1): 插件与JavaScript扩展

虽然目前的浏览器的功能很强 ,但仍然有其局限性。早期的浏览器能力十分有限,Web前端开发者希望能够通过一定的机制来扩展浏览器的能力。早期的方法就是插件机制,现在流行次啊用混合编程(Hybird Programming)模式。插件…

小微企业科技创新之策略:人才、投入、模式、技术、支持四管齐下

对于小微企业来说,搞科技创新需要从多个方面入手。以下是一些可供参考的方法: 明确创新方向:首先,企业需要明确自己的创新方向和目标,这有助于聚焦资源,避免盲目投入。同时,企业需要对市场进行…

charles使用指南

一、什么是charles Charles 是一个http代理、管理以及反向代理工具,它允许开发者查看本地机器和互联网之间的关于http、https的所有通信,包含请求、响应以及他们的请求头、响应头。 它的主要功能包含: 1、SSL代理 2、带宽限制 3、ajax断…

如何查看Linux CPU占有率

目录 1、top 2、htop 3、vmstat 4、mpstat 5、iostat 查看嵌入式设备CPU占有率是评估系统资源使用情况的重要方式。 在Linux系统中,有多种方法可以查看CPU占有率,这里介绍几种常用的命令行工具。 1、top 这是最常用的命令之一,它提供了…

go语言(十三)-----interface

一、Interface 通用万能类型 空接口int,string,float,struct都实现了interface都可以用interface{}类型,引用任意的数据类型 package mainimport "fmt"//interface()是万能数据类型 func myFunc(arg interface{}) {fmt.Println(&…

[娱乐]索尼电视安装Kodi

索尼电视不能直接apk安装kodi应用 android studio安装后附带 abd, 路径 C:\Users\[yourname]\AppuoData\Local\Android\Sdk\platform-tools\adb.exe安卓电视点击内部版本号,启用开发者模式 adb 连接索尼安卓电视,记得电视上运行调试 abi选…

函数传参数组时,使用数组形参的本质

c语言中函数的调用分为两种方式:传址调用、传值调用。 传值调用我们都知道就是将实参的值传送给被调函数,让被调函数的形参接收这个值,从而形参内存中的数据就变成了实参的一份拷贝。 而传址调用则是将实参的地址传送过去,然后令…