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

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.mzph.cn/news/641132.shtml

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

相关文章

react 实现页面状态缓存(keep-alive)

前言: 因为 react、vue都是单页面应用,路由跳转时,就会销毁上一个页面的组件。但是有些项目不想被销毁,想保存状态。 比如:h5项目跳转其他页面返回时,页面状态不丢失。设想一个 页面我滑倒了中间&#xf…

仓储管理系统——软件工程报告(需求分析)②

需求分析 一、系统概况 仓库管理系统是一种基于互联网对实际仓库的管理平台,旨在提供一个方便、快捷、安全的存取货物和查询商品信息平台。该系统通过在线用户登录查询,可以线上操作线下具体出/入库操作、查询仓库商品信息、提高仓库运作效率&#xff…

shell脚本概述

将命令写到脚本里面,利用路径或者解释器去执行。简要来说脚本其实就是命令的集合。 例如:echo $? 自定义变量,查看上次命令执行是否正确 linux常用的shell 脚本的构成: 1.解释器 (脚本是用什么语言写的…

10个常用python自动化脚本

大家好,Python凭借其简单和通用性,能够为解决每天重复同样的工作提供最佳方案。本文将探索10个Python脚本,这些脚本可以帮助自动化完成任务,提高工作效率。无论是开发者、数据分析师还是仅仅想简化工作流程的普通用户,…

【数据结构】二叉树算法讲解(定义+算法原理+源码)

博主介绍:✌全网粉丝喜爱、前后端领域优质创作者、本质互联网精神、坚持优质作品共享、掘金/腾讯云/阿里云等平台优质作者、擅长前后端项目开发和毕业项目实战✌有需要可以联系作者我哦! 🍅附上相关C语言版源码讲解🍅 &#x1f44…

Java - 深入四大限流算法:原理、实现与应用

文章目录 Pre概述简单计数器原理实现测试优缺点 滑动窗口算法原理实现测试优缺点 漏桶算法原理实现测试优缺点 令牌桶算法原理实现测试优缺点 小结 Pre 深入理解分布式技术 - 限流 并发编程-25 高并发处理手段之消息队列思路 应用拆分思路 应用限流思路 SpringBoot - 优雅…

画眉(京东科技设计稿转代码平台)介绍

前言 随着金融App业务的不断发展,为了满足不同场景下的用户体验及丰富的业务诉求,业务产品层面最直接体现就是大量新功能的上线及老业务的升级,随之也给研发带来了巨大的压力,所以研发效率的提升就是当前亟需解决的问题&#xff…

QGIS生成热力图

目录 1 QGIS介绍 2 实现效果 3 具体步骤 3.1 获取北京市地图(区县级) 3.1.1 方法一:直接找到北京市地图 3.1.2 方法二:若没有单独的北京市地图,从中国地图上提取 3.2 获取数据 3.3 导入数据 1 QGIS介绍…

1148. 秘密的牛奶运输 (次小生成树)

1148. 秘密的牛奶运输 - AcWing题库 农夫约翰要把他的牛奶运输到各个销售点。 运输过程中,可以先把牛奶运输到一些销售点,再由这些销售点分别运输到其他销售点。 运输的总距离越小,运输的成本也就越低。 低成本的运输是农夫约翰所希望的。…

知识笔记(九十六)———在vue中使用echarts

1、安装 npm install echarts --save 2、在vue中引入(全局引入) // 引入echarts import echarts from echarts Vue.prototype.$echarts echarts3、在vue中的使用 需要用到echart的地方先设置一个div的id、宽高 提示: 可以在一个页面中引…

数据结构之使用顺序表写出通讯录

前言 昨天我们踏入了数据结构的深山,并且和顺序表battle了一番,虽说最后赢了,但同时也留下了一个问题:如何从顺序表的增删查改加强到通讯录的的增删查改,别急,今天就带你一探究竟。 一.回顾与思考 我们昨…

geemap学习笔记053:纹理特征

前言 纹理特征通常描述了遥感影像中像素之间的空间关系和变化,对于地物分类、目标检测以及图像分割等遥感应用非常有价值。本节将会介绍Earth Engine中提供的一些纹理特征计算方法,包括熵和灰度共生矩阵。 1 导入库并显示数据 import ee import geema…

【UE5】第一次尝试项目转插件(Plugin)的时候,无法编译

VS显示100条左右的错误,UE热编译也不能通过。原因可能是[名字.Build.cs]文件的错误,缺少一些内容,比如说如果要写UserWidget类,那么就要在 ]名字.Build.cs] 中加入如下内容: public class beibaoxitong : ModuleRules …

自己本机Video retalking制作数字人

首先需要注意的是,这个要求你的笔记本显存和内存都比较大。我的电脑内存是64G,显卡是8G,操作系统是Windows 11,勉强能够运行出来,但是效果不是很好。 效果如下,无法上传视频,只能通过图片展示出…

[分章:阅读]《我的第一本算法书》

第一章数据结构 1.链表 1、数据结构之一,线性排列数据,指针链接数据;访问O(n),删除/添加O(1) 2、类似链条。 2.数组 1、线性排列数据,含数据下标(即索引&…

C++如何在0和1之间取随机数

在C中&#xff0c;你可以使用 <random> 库来生成0和1之间的随机数。这个库提供了各种生成随机数的方法&#xff0c;包括均匀分布的随机数。 下面是一个简单的例子&#xff0c;展示如何使用 <random> 库来生成0和1之间的随机数&#xff1a; cpp复制代码 #include…

二、docker的常用命令(持续补充img)

目录 一、启动相关1.设置容器开机启动 二、查询相关1.查询所有容器&#xff08;包括停止的&#xff09; 三、修改相关1.指定容器开机自启动 一、启动相关 1.设置容器开机启动 在我们使用镜像run一个容器的时候&#xff0c;希望这个容器随着docker的启动而启动&#xff08;我的…

Ubuntu20.04.3LTS桌面版与Window10双系统并存

Ubuntu20.04.3LTS桌面版与Window10双系统并存 文章目录 Ubuntu20.04.3LTS桌面版与Window10双系统并存1.分区与安装1. 硬盘分区1. 一般用途2. 服务器用 2. 操作系统版本及分区信息3. 安装时创建用户4. 安装后修改root设置用户密码&#xff1a;3. 安装时指定ip4. 设置静态IP 2. 安…

【leetcode100-051到054】【图论】四题合集

【岛屿数量】 给你一个由 1&#xff08;陆地&#xff09;和 0&#xff08;水&#xff09;组成的的二维网格&#xff0c;请你计算网格中岛屿的数量。 岛屿总是被水包围&#xff0c;并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外&#xff0c;你可以假设…

Java线程池七大参数详解和配置(面试重点)

一、corePoolSize核心线程数 二、maximunPoolSize最大线程数 三、keepAliveTime空闲线程存活时间 四、unit空闲线程存活时间的单位 五、workQueue线程工作队列 1、ArrayBlockingQueue FIFO有界阻塞队列 2、LinkedBlockingQueue FIFO无限队列 3、PriorityBlockingQueue V…