《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࿰…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...
连锁超市冷库节能解决方案:如何实现超市降本增效
在连锁超市冷库运营中,高能耗、设备损耗快、人工管理低效等问题长期困扰企业。御控冷库节能解决方案通过智能控制化霜、按需化霜、实时监控、故障诊断、自动预警、远程控制开关六大核心技术,实现年省电费15%-60%,且不改动原有装备、安装快捷、…...
拉力测试cuda pytorch 把 4070显卡拉满
import torch import timedef stress_test_gpu(matrix_size16384, duration300):"""对GPU进行压力测试,通过持续的矩阵乘法来最大化GPU利用率参数:matrix_size: 矩阵维度大小,增大可提高计算复杂度duration: 测试持续时间(秒&…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
JAVA后端开发——多租户
数据隔离是多租户系统中的核心概念,确保一个租户(在这个系统中可能是一个公司或一个独立的客户)的数据对其他租户是不可见的。在 RuoYi 框架(您当前项目所使用的基础框架)中,这通常是通过在数据表中增加一个…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...

ubuntu22.04有线网络无法连接,图标也没了
今天突然无法有线网络无法连接任何设备,并且图标都没了 错误案例 往上一顿搜索,试了很多博客都不行,比如 Ubuntu22.04右上角网络图标消失 最后解决的办法 下载网卡驱动,重新安装 操作步骤 查看自己网卡的型号 lspci | gre…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...