《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、句柄数据表示等方面,我们在前面都一一做了介绍,读者可以回忆一番。我们前面已经介绍了以上两个引擎的很多特点和好处,笔者还希望留一些想象的空间,让读者自己体会上面这些技术细节带来的潜在优势和缺点,以及潜在的发展方向。
相关文章:

《WebKit 技术内幕》学习之九(3): JavaScript引擎
3 JavaScriptCore引擎 3.1 原理 JavaScriptCore引擎是WebKit中的默认JavaScript引擎,也是苹果在开源WebKit项目之后,开源的另外一个重要的项目。同其他很多引擎一样,在刚开始的时候它的主要部分是一个基于抽象语法树的解释器,这…...

IS-IS:05 ISIS开销值和协议优先级
IS-IS 协议为路由器的每个 IS-IS 接口定义并维护了一个 level-1 开销值和一个 level-2开销值。开销值可以在接口上或者全局上手动配置,也可以使用 auto-cost自动计算确定。 修改接口cost: int g0/0/0 isis cost 50修改全局cost: isis cir…...

群辉NAS的远程访问
群辉NAS是私有云存储,局域网访问很容易【详见:网上邻居访问设置、其它设备的访问设置】,远程访问相对复杂,涉及很多关键因素,现将过程记录如下: 目录 1、互联网接入 2、绑定MAC与IP地址 3、路由器开启5…...

构建未来学堂:在线教育系统开发技术实践
在当今数字化时代,在线教育系统的开发越发显得至关重要。本文将带你深入了解在线教育系统的开发,涉及到关键的技术实践和代码示例。我们将采用现代化技术栈,为未来学堂的搭建提供实用的指南。 技术栈选择 在开始实际的开发之前,…...

EMQX 单机及集群搭建
目录 1. 通过 Yum 源安装(CentOS7 单机安装) 1.1. 通过以下命令配置 EMQX Yum 源: 1.2. 运行以下命令安装 EMQX: 1.3. 运行以下命令启动 EMQX: 1.4. 访问 http://192.168.88.130:18083,默认用户名: adm…...

SpringMVC-对静态资源的访问
1.工程中加入静态资源 在webapp下创建static文件夹,此文件夹专门放入静态资源 2.使项目可以处理静态资源的请求 在SpringMVC配置文件中添加以下语句 1.引入命名空间 xmlns:mvc"http://www.springframework.org/schema/mvc" xsi:schemaLocation“http…...
形参和实参
目录 形参(形式参数): 实参(实际参数): 形参和实参的区别和联系 具体实例: 当我们谈论"形参"和"实参"时,可以用以下类比来解释它们之间的关系: 形参…...

[git] windows系统安装git教程和配置
一、何为Git Git(读音为/gɪt/)是一个开源的分布式版本控制系统,可以有效、高速地处理从很小到非常大的项目版本管理。 二、git安装包 有2种版本,Git for Windows Setup和Git for Windows Portable(便携版)两个版本都可以。 三、Git for Windows Por…...

php的性能要比node.js高很多吗?
在当今的编程世界中,PHP和Node.js是两种广泛使用的服务器端编程语言。虽然它们都用于构建高效的网络应用程序,但关于它们性能的争论一直存在。有些人认为PHP的性能要比Node.js高很多,而另一些人则持相反意见。 性能通常指的是计算机程序或系…...

[极客大挑战 2019]BabySQL1
发现union select被过滤了,双写绕过 or、from被过滤 where被过滤 在b4bysql中找到flag...

机器视觉在OCR字符检测的应用
在产品质量 检测过程中,对于字符、条码等标识信息的识别、读取、检测是非常重要的一部分,比如在食品饮料包装检测中,生产日期 、保质期 、生产批号 、条码等字符信息是产品管理和追溯必不可缺的,因此利用机器视觉技术进行OCR字符采…...
讲清楚浅拷贝和深拷贝
先放出实现浅拷贝和深拷贝的一些方法(直接食用): 1.浅拷贝: 浅拷贝在拷贝对象的时候,对于对象最外一层实现的是普通的值拷贝,对于对象里面的对象是浅拷贝,只复制地址不复制地址对应的值。 /* 方法1.1:扩…...

[足式机器人]Part2 Dr. CAN学习笔记- 最优控制Optimal Control Ch07
本文仅供学习使用 本文参考: B站:DR_CAN Dr. CAN学习笔记 - 最优控制Optimal Control Ch07-1最优控制问题与性能指标 1. 最优控制问题与性能指标2. 动态规划 Dynamic Programming2.1 基本概念2.2 代码详解2.3 简单一维案例 3. 线性二次型调节器ÿ…...

RedisInsight详细安装教程
简介 RedisInsight 是一个直观高效的 Redis GUI 管理工具,它可以对 Redis 的内存、连接数、命中率以及正常运行时间进行监控,并且可以在界面上使用 CLI 和连接的 Redis 进行交互(RedisInsight 内置对 Redis 模块支持)。 RedisIn…...
maven组件升级报错经验汇总
1. NosuchMethodError org.springframework.beans.factory.support.genericBeanDefinition(xxxxx) 2. ClassNotFoundException: org.springframework.boot.SpringApplication 可能冲突的依赖是: <dependency><groupId>org.springframework.boot</g…...
JS 中的 async 与 await
课程地址 有 4 个返回 Promise 对象的函数 ABCD,现在想让这 4 个 Promise 顺序执行: const isA true; const isB true; const isC true; const isD true;function A() {return new Promise((resolve, reject) > {console.log("running A&q…...
SQL 系列教程(六)
目录 SQL FOREIGN KEY 约束 SQL FOREIGN KEY 约束 创建表时的 FOREIGN KEY 约束 修改表时的 FOREIGN KEY 约束 撤销 FOREIGN KEY 约束 SQL CHECK 约束 SQL CHECK 约束 创建表时的 CHECK 约束 修改表的 CHECK 约束 撤销 CHECK 约束 SQL DEFAULT 约束 SQL DEFAULT 约…...

CocoaPods的安装和使用
前言 本篇文章讲述CocoaPods的安装和使用 安装cocoaPods 如果电脑没有安装过cocoaPods,需要先安装,使用下面的命令: sudo gem install cocoapods输入密码后开始安装,需要等待。。。但是我这里报错了。 The last version of d…...

Linux下软件安装的命令【RPM,YUM】及常用服务安装【JDK,Tomcat,MySQL】
Linux下软件安装的命令 源码安装 以源代码安装软件,每次都需要配置操作系统、配置编译参数、实际编译,最后还要依据个人喜好的方式来安装软件。这个过程很麻烦很累人。 RPM软件包管理 RPM安装软件的默认路径: 注意: /etc 配置文件放置目录…...

【linux】-telnet服务安装
1. 说明 telnet 分为 :telnet 服务端 和 telnet 客户端 本文只演示安装 telnet服务端 2. 安装telnet服务端、以及守护服务xinetd 2.1 检测telnet-server的rpm包是否安装 rpm -qa telnet-server 2.2 若未安装,则安装telnet-server࿰…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...

cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...

srs linux
下载编译运行 git clone https:///ossrs/srs.git ./configure --h265on make 编译完成后即可启动SRS # 启动 ./objs/srs -c conf/srs.conf # 查看日志 tail -n 30 -f ./objs/srs.log 开放端口 默认RTMP接收推流端口是1935,SRS管理页面端口是8080,可…...
sqlserver 根据指定字符 解析拼接字符串
DECLARE LotNo NVARCHAR(50)A,B,C DECLARE xml XML ( SELECT <x> REPLACE(LotNo, ,, </x><x>) </x> ) DECLARE ErrorCode NVARCHAR(50) -- 提取 XML 中的值 SELECT value x.value(., VARCHAR(MAX))…...

SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...

使用LangGraph和LangSmith构建多智能体人工智能系统
现在,通过组合几个较小的子智能体来创建一个强大的人工智能智能体正成为一种趋势。但这也带来了一些挑战,比如减少幻觉、管理对话流程、在测试期间留意智能体的工作方式、允许人工介入以及评估其性能。你需要进行大量的反复试验。 在这篇博客〔原作者&a…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...