浏览器原理之V8引擎

article/2025/8/21 14:34:56

主流 JS 引擎

JavaScript 的主流引擎如下所示:

  • V8 (Google) 
  • SpiderMonkey (Mozilla) 
  • JavaScriptCore (Apple) 
  • Chakra (Microsoft) 
  • duktape(IOT)
  • JerryScript(IOT)
  • QuickJS
  • Hermes(Facebook-React Native)

V8 之 what & why & when?

waht:V8是一个由Google开源的采用C++编写的高性能JavaScriptWebAssembly引擎,应用在 ChromeNode.js等中。它实现了ECMAScriptWebAssembly,运行在Windows 7及以上、macOS 10.12+以及使用x64、IA-32、ARMMIPS处理器的Linux系统上。 V8可以独立运行,也可以嵌入到任何C++应用程序中。

when:V8最初是由Lars Bak团队开发的,以汽车的V8发动机(有八个气缸的V型发动机)进行命名,预示着这将是一款性能极高的JavaScript引擎,在2008年9月2号chrome一同开源发布。

why:JavaScript代码最终要在机器中被执行,需要经过一系列的处理,将高级语言转换成二进制码指令,机器才可以识别和执行。而转换过程由 V8 负责完成

V8的发布流程

V8 团队使用 4 种 Chrome 发布渠道向用户推送新版本。

  • Canary releases 金丝雀版 (每天):最新 origin 也就是 master分支。
  • Dev releases 开发版 (每周)
  • Beta releases 测试版 (每 6 周)
  • Stable releases 稳定版 (每 6 周):branch-heads分支,作为生产环境应该使用的版本。

V8的组成部分

V8的内部有很多模块,其中最重要的4个如下:

  • Parser: 解析器,负责将源代码解析成AST;
  • Ignition: 解释器,负责将AST转换成字节码并执行,同时会标记热点代码;
  • TurboFan: 编译器,负责将热点代码编译成机器码并执行;
  • Orinoco: 垃圾回收器,负责进行内存空间回收;

以下是 V8 中这几个重要模块的具体工作流程图。接下来我们除垃圾回收器 Orinoco 之外逐个进行分析。

Parser 解析器

Parser解析器的转换过程有两个重要阶段:词法分析(Lexical Analysis)语法分析(Syntax Analysis)。

词法分析阶段是扫描输入的源代码字符串,生成一系列的词法单元 (tokens),这些词法单元包括数字,标点符号,运算符等。词法单元之间都是独立,该阶段并不关心代码的组合方式。

JavaScript中的token主要包含以下几种:

关键字:var、let、const等;

标识符:没有被引号括起来的连续字符,可能是一个变量,也可能是 if、else 这些关键字,又或者是 true、false 这些内置常量;

运算符: +、-、 *、/ 等;

数字:像十六进制,十进制,八进制以及科学表达式等;

字符串:变量的值等;

空格:连续的空格,换行,缩进等;

注释:行注释或块注释都是一个不可拆分的最小语法单元;

标点:大括号、小括号、分号、冒号等。

比如, const name = 'zedran'经过esprima词法分析后生成的tokens:

[{"type": "Keyword","value": "const"},{"type": "Identifier","value": "name"},{"type": "Punctuator","value": "="},{"type": "String","value": "'zedran'"}
]

语法分析阶段是将词法分析产生的token按照某种给定的形式文法转换成AST的过程。也就是把单词组合成句子的过程。在转换过程中会验证语法,语法如果有错的话,会抛出语法错误。 

上述const name = 'zedran'经过语法分析后生成的AST如下:

{"type": "Program","start": 0,"end": 21,"body": [{"type": "VariableDeclaration","start": 0,"end": 21,"declarations": [{"type": "VariableDeclarator","start": 6,"end": 21,"id": {"type": "Identifier","start": 6,"end": 10,"name": "name"},"init": {"type": "Literal","start": 13,"end": 21,"value": "zedran","raw": "'zedran'"}}],"kind": "const"}],"sourceType": "module"
}

主流的 JavaScript 引擎都采用了惰性解析(Lazy Parsing),因为源码在执行前如果全部完全解析的话,不仅会造成执行时间过长,而且会消耗更多的内存以及磁盘空间。

惰性解析就是指如果遇到并不是立即执行的函数,只会对其进行预解析(Pre-Parser),当函数被调用时,才会对其完全解析。

预解析时,只会验证函数的语法是否有效、解析函数声明以及确定函数作用域,并不会生成 AST,这项工作由 Pre-Parser 预解析器完成。

此后,经过Parser解析器生成的AST将交由Ignition解释器进行处理。

Ignition 解释器

Ignition 解释器负责AST转换成字节码(Bytecode)并执行。字节码是介于AST和机器码之间的一种代码,与特定类型的机器代码无关,需要通过解释器转换成机器码才可以执行。

聪明的你可能会问:为何不直接将AST转换成机器码直接运行呢?

V8的 5.9 版本之前是没有字节码的,而是直接将JS代码编译成机器码并将机器码存储到内存中,这样就占用了大量的内存,而早期的手机内存都不高,过度的占用会导致手机性能大大的下降;而且直接编译成机器码导致编译时间长,启动速度慢;再者直接将JS代码转换成机器码需要针对不同的CPU架构编写不同的指令集,复杂度很高。

5.9 版本以后引入了字节码,可以解决上述内存占用大、启动时间长、代码复杂度高这几个问题。

Ignition解释器的工作流程图:

AST需要先通过字节码生成器,再经过一系列的优化之后才能生成字节码。 其中的优化包括:

  • Register Optimizer:主要是避免寄存器不必要的加载和存储
  • Peephole Optimizer:寻找字节码中可以复用的部分,并进行合并
  • Dead-code Elimination: 删除无用的代码,减少字节码的大小

Ignition 解释器在执行字节码的过程中,会监视代码的执行情况并记录执行信息,如函数的执行次数、每次执行函数时所传的参数等。被执行多次的同一段代码,会被标记成热点代码,交给TurboFan编译器进行处理。

TurboFan 编译器

TurboFan 编译器拿到 Ignition 解释器标记的热点代码后,会先优先将优化后字节码编译成更高效的机器码存储起来。下次再次执行相同代码时,会直接执行相应的机器码,大大提升了代码的执行效率。即编译执行。

当一段代码不再是热点代码后,TurboFan会将优化编译后的机器码还原成字节码,将代码的执行权利交还给 Ignition 解释器。即解释执行。

现在我们来看一看具体的执行过程:以多次执行 sum = sum + arr[i] 为例,JS是动态类型的语言,每次的 sum 和 arr[i] 都有可能是不同的类型,在执行这段代码时,Ignition 每次都会检查 sum 和 arr[i] 的数据类型。其被标记为热点代码,交给 TurboFan 编译器直接以之前的几次执行确定 sum 和 arr[i] 的数据类型编译成机器码。

但如果在后续的执行过程中,arr[i] 的数据类型发生了改变,之前生成的机器码就不满足要求了,TurboFan 编译器会把之前生成的机器码丢弃,将执行权利再交给  Ignition 解释器,完成去优化的过程。

解释执行启动速度快,执行速度慢,而编译执行启动速度慢,执行速度快。这种将字节码与解释器(解释执行)和编译器(编译执行)结合的混合技术,就是我们通常所说的即时编译(JIT)

高性能的 JS 引擎不仅需要 TurboFan 这样高度优化的编译器,在编译器有机会开始工作之前的性能,也存在着大量的优化空间。于是在 2021 年,V8 引入新的编译管道 Sparkplug。

内联优化

function add(a, b) {return a + b
}
function foo() {return add(2, 4)
}

如上代码所示,在 foo 函数中调用了函数 add,add 函数接收 a,b 两个参数,返回他们的和。如果不经过编译器优化,则会分别生成这两个函数所对应的机器码。

为了提升性能,TurboFan 优化编译器会将上面两个函数进行内联,然后再进行编译。内联优化后,编译生成的机器码会精简很多,执行效率也有很大的提升:

/** 因为 fooAddInlined 中 a 和 b 是确定值 */
function fooAddInlined() {return 6
}

逃逸分析

逃逸分析就是分析对象的生命周期是否仅限于当前函数,是则被认为是“未逃逸”的。

function add(a, b){const obj = { x: a, y: b }return obj.x + obj.y
}

可以将上面代码进行优化:

function add(a, b){const obj_x = aconst obj_y = breturn obj_x + obj_y
}

优化后,无需对象定义,可以直接将变量加载到寄存器上,不再需要从内存中访问对象属性。不仅减少了内存消耗,而且提升了执行效率。

其他优化

除了上述提到的各种优化方案和模块,V8 还有很多优化手段和核心模块,如:使用隐藏类快速获取对象属性、使用内联缓存提升函数执行效率、Orinoco 垃圾回收器、Liftoff WebAssembly 编译器

总结

V8 的执行过程:

  1. 源代码经过 Parser 解析器,经过词法分析和语法分析生成 AST;
  2. AST经过 Ignition 解释器生成字节码并执行;
  3. 在执行过程中,如果发现热点代码,将热点代码交给 TurboFan 编译器生成机器码并执行;
  4. 如果热点代码不再满足要求,进行去优化处理。

http://chatgpt.dhexx.cn/article/JuM0PvKc.shtml

相关文章

V8引擎工作原理

V8引擎工作原理 1.V8引擎是干什么的? V8的工作是将js代码翻译为CPU指令。 2.V8工作过程 Blink将js源码交给V8引擎 Stream对js源码编码 Scanner进行词法分析,将代码转为token token转化为AST树 Parser将tokens直接转化为AST树PreParser(…

谷歌v8引擎详解

前言 JavaScript绝对是最火的编程语言之一,一直具有很大的用户群,随着在服务端的使用(NodeJs),更是爆发了极强的生命力。编程语言分为编译型语言和解释型语言两类,编译型语言在执行之前要先进行完全编译&am…

V8 引擎是如何工作的?

作者 | Fundebug 最近,JavaScript生态系统又多了2个非常硬核的项目。 大神Fabrice Bellard发布了一个新的JS引擎QuickJS,可以将JavaScript源码转换为C语言代码,然后再使用系统编译器(gcc或者clang)生成可执行文件。 Facebook为React Native开…

V8 JS引擎

一. Google开发V8 Google (丹麦)研发小组在 2006 年开始研发 V8 ,部分的原因是 Google 对既有 JavaScript 引擎的执行速度不满意, 在2008年推出chrome, 巨大的速度优势, 迅速占领市场. 2017年chrome的市场占有达到59%. 二. 常见JS引擎 JScript(IE6,IE7, IE8) …

前端深度之Chrome的V8引擎内存详解

内容:V8引擎、JS内存管理、V8引擎如何回收垃圾、如何查看V8内存使用情况、内存优化实例 目录 一、V8引擎是什么? 二、内存 2.1、内存生命周期:(这个不同的程序语言基本一样) 2.2、JavaScript的内存管理 2.3、为什…

linux硬盘格式改为xfs,如何给Linux系统配置XFS文件系统

现在的系统基本都配备了XFS文件系统,XFS文件系统因其优秀且功能丰富而使用的人数很多,下面小编就给大家介绍下Linux下如何配置XFS文件,给想要配置XFS的朋友做个参考。 一、XFS文件系统简介 主要特性包括以下几点: 数据完全性 采用XFS文件系统,当意想不到的宕机发生后,首先…

xfs_growfs

LVM扩容 1 检查当前使用情况 df -h lvs vgs 2 在线扩容/opt lvextend -L 100GB /dev/mapper/VolGroup-opt xfs_growfs /dev/mapper/VolGroup-opt 3 扩容后检查 lvs df -hdf -hT lvslvextend -L 100GB /dev/mapper/VolGroup-optxfr_growfs /dev/mapper/VolGroup-optdf -hT # xf…

linux7xfs,RHEL7--XFS FileSystem

XFS 介绍 在RHEL7中,把XFS作为一种高可用性,高扩展性的文件系统已经列为默认的首先文件系统类型 XFS支持元数据日志记录,支持在线的扩容和整理. 可用采用 mkfs.xfs device 来进行格式 XFS Quota Management xfs能够汇报出在用户&#xff0c…

时代催生枭雄——XFS应运而生

站在人类发展进程的角度,全球新冠肺炎疫情大势横行,促进了百年未有之大变局加速演变,全球原有格局打破,新型的世界秩序正在重塑,世界又站在了历史的十字路口。 中国经济的发展需要良好的营商环境,对虚拟货…

Linux之XFS文件恢复

xfs类型文件备份恢复 我们使用的centos 7 默认使用的是xfs 类型文件系统,可以用xfsdump 与 xfsrestore 工具进行备份恢复。 xfsdump 的备份级别:0 :表示完全备份。1-9 表示增量备份。xfsdump 的备份级别默认为0。 xfsdump 的命令格式&…

xfs mysql_XFS文件系统与mysql应用性能测试

XFS 最初是由 Silicon Graphics,Inc. 于 90 年代初开发的。那时,SGI 发现他们的现有文件系统(existing filesystem,EFS)正在迅速变得不适应当时激烈的计算竞争。为解决这个问题,SGI 决定设计一种全新的高性能 64 位文件系统&#…

关于XFS文件系统概述

前言: 目前XFS已成为Linux主流的文件系统,所以有必要了解下其数据结构和原理。 XFS文件系统 XFS是一个日志型的文件系统,能在断电以及操作系统崩溃的情况下保证数据的一致性。XFS最早是针对IRIX操作系统开发的,后来移植到linux上,目前CentOS 7已将XFS作为默认的文件系统…

XFS 文件系统 (一) :设计概览

文章目录 0 前言1 设计背景2. 需要解决的问题2.1 异常恢复太慢2.2 不支持大文件系统2.3 不支持大型稀疏文件2.4 不支持大型连续文件2.5 不支持大目录2.6 不支持过多文件个数 3 XFS 架构4 痛点解决4.1 Allocation Groups4.2 Manging Free Space4.3 大文件的支持 5 总结 0 前言 …

linux常见实战(一)--【基于centos7的磁盘操作(新磁盘挂载/已有磁盘扩容)】

文章目录 前言:磁盘与文件系统1.xfs2.ext4 一、新磁盘的挂载1.查看已挂载磁盘情况2.查看所有磁盘情况3.给新磁盘分区4.格式化磁盘5.挂载磁盘6.设置开机自动挂载7.测试重启后挂载状态 二、对已有磁盘进行扩容1.查看磁盘挂载情况、分区情况2.进入磁盘进行分区操作3.格…

更快更好的Linux文件系统:XFS

XFS一种高性能的日志文件系统,最早于1993年由Silicon Graphics为他们的IRIX操作系统而开发,是IRIX 5.3版的默认文件系统。2000年5月,Silicon Graphics以GNU通用公共许可证发布这套系统的源代码,之后被移植到Linux 内核上。XFS极具…

Centos7使用

目录 一.材料准备 二.安装VM 三.创建虚拟机 四.进入CentOS安装界面 五、最小化必要的一些工具 一.材料准备 1.VMware-workstation 16版本 2.CentOS-7 64位光盘映像文件 二.安装VM 略 三.创建虚拟机 1.打开VMware Workstation,点击创建虚拟机 2.选择自定义…

CentOS7安装MySQL8

文章目录 一 前言二、Centos 7 安装 mysql8 步骤:1.下载MySQL官方的 Yum Repository2.安装方法一: 用wget 下载后安装方法二:下载 RMP 软件包将该软件包上传到 Linux 服务器,并安装。 3.Navicate 远程连接配置 一 前言 最近在自己…

CentOS7的安装流程

CentOS7的安装流程 1、 环境准备 安装VMware 下载Centos7镜像文件 2、 CentOS7安装流程 步骤一:打开VMware,点击创建新的虚拟机 步骤二:选择典型安装,点击下一步 步骤三:选择稍后安装操作系统,点击…

centos7安装教程

centos7安装教程:首先下载“CentOS-7-x86_64-bin-DVD1.iso”镜像文件;然后依次选择“文件->新建虚拟机->自定义”;接着根据向导进行虚拟机的创建;最后进入CentOS安装界面并进行安装设置即可。 本文操作环境:cent…

Linux-CentOS7安装教程【附CentOS7镜像】

Linux-CentOS7安装教程【附CentOS7镜像】 1. 新建虚拟机 点击“创建新的虚拟机” 点击“下一步” 选择“稍后安装操作系统”,点击“下一步” 选择"CentOS 7 64",点击“下一步” 起一个虚拟机名字,设置一个合适的安装位置&#xf…