写点什么

JavaScript 引擎分析

  • 2019-08-22
  • 本文字数:4121 字

    阅读完需:约 14 分钟

JavaScript引擎分析

一.JavaScript 简介

JavaScript 是一种动态类型的脚本语言;在 1995 年时,由 Netscape 公司的 Brendan Eich,在网景导航者浏览器上首次设计实现而成。因为 Netscape 与 Sun 合作,Netscape 管理层希望它外观看起来像 Java,因此取名为 JavaScript。


JavaScript 脚本语言具有以下特点:


(1)脚本语言。JavaScript 是一种解释型的脚本语言,是在程序的运行过程中逐行进行解释执行,不需要预编译。;而 Java、C++等语言需要先编译后执行;


(2)动态性。JavaScript 能够动态修改对象的属性,没有办法在编译的时候知道变量的类型,只有在运行的时候才能确定;而 Java、C++等都是静态类型语言,他们在编译的时候就能够知道每个变量的类型;


(3)跨平台性。JavaScript 脚本语言不依赖于操作系统,仅需要浏览器的支持。可以在多种平台下运行(如 Windows、Linux、Mac、Android、IOS 等)

二.JavaScript 与 Java 语言区别

从上面介绍的 JavaScript 语言特点会发现 JavaScript 的效率会比 Java、C++低很多;看以下这个实例:



当 JavaScript 引擎分析到该段代码的时候,根本不知道 a 和 b 是什么类型,唯一的办法就是运行的时候根据实际传过来的对象再来计算,这显然会导致严重的性能问题;



当编译上面 Java 代码的时候,根据右边类型 Class1 的定义,获取对象 a 的属性 x 的时候,其实就是对象 a 的地址,大小是一个整形。同时获取对象 b 的属性 y 的时候,其实就是对象 b 的地址加上 4 个字节,这些都是在生成本地代码的时候确定的,无需再运行本地代码的时候再决定他们的地址和类型是什么,这显然能够节省时间;


再看一下两者分别是怎样存储对象 a 和 b 的:


对于传统的 JavaScript 解释器来说,因为不知道 a 和 b 的具体类型,就用属性名-属性值对来保存,之后访问对象的属性值时就需要通过属性名匹配来获取对应的值;对象 b 也是同样的结果来保存相同的属性;随着对象的增多,这显然带来了巨大的空间浪费;



而上面的 Java 代码在编译时就确定了类 Class1 的成员类型,访问 x 就是对象 a 的地址,y 就是 a 的地址加上 4 个字节;所以字符“x”和“y”运行时都不在需要;因为不再需要额外的查找这些属性地址的工作;



从上面实例可以看到 JavaScript 和 Java 语言区别包括以下几个部分:


编译确定位置: Java 有编译和执行两个阶段,位置的偏移信息都是在编译器编译的时候决定的,当 java 生成本地代码之后,对象的属性和偏移信息都计算完成;而 JavaScript 没有类型,只有在对象执行创建的时候才确定这些信息,而且 JavaScript 语言能够在执行的时候修改对象的属性。


偏移信息共享: Java 有类型定义,所有的对象都是共享偏移信息的;访问他们只需要按照编译时确定的偏移量即可。JavaScript 则不同,每个对象都有自我描述,属性和位置偏移信息都包含在自身的结构中。


偏移信息查找: Java 查找偏移地址很简单,都是在编译代码时,对使用到的类型成员变量直接设置偏移量;而 JavaScript 则需要通过属性名匹配才能查找到对应的值。


Java 语言有明显的两个阶段:编译和运行,如下图所示:



Java 代码经过编译器编译之后生成的是字节码,字节码是跨平台的一种中间表示,不同于本地代码。该字节码于平台无关,能够在不同的操作系统上运行。在运行字节码阶段,Java 的运行环境是 Java 虚拟机加载字节码。Java 虚拟机一般都引入 JIT 技术来将字节码转变成本地代码来提高执行效率。第一阶段对时间要求不严格,第二阶段对每个步骤所花费的时间非常敏感,时间越短越好。


JavaScript 语言的编译和执行都是在运行阶段执行的,如下图所示:



因为都是在代码运行过程中来处理这些步骤,所以每个阶段的时间越短越好,而且每引入一个阶段都是额外的时间开销;所以一个 JavaScript 引起主要包含以下几个部分:


编译器: 主要工作是将源代码编译成抽象语法树;


解释器: 主要是接受字节码,解释执行这个字节码;


JIT 工具: 将字节码或抽象语法树转换成本地代码;


垃圾回收期和分析工具(Profiler): 负责垃圾回收和收集引擎中的信息,帮助改善引擎的性能;

三.V8 引擎介绍

V8 是一个 JavaScript 引擎实现的开源项目,最开始由一些语言学家设计出来,后被 Google 收购,成为了 JavaScript 引擎和众多相关技术的引领者。V8 支持众多的操作系统,包括 Windows、Linux、Android、Mac OS X 等;同时它也能够支持众多的硬件架构 IA32、X64、ARM、MIPS 等,他将主流软硬件平台一网打尽,由于它是一个开源项目,开发者可以自由使用它的强大能力,目前炙手可热的 NodeJs 项目就是基于 V8 项目研发的。

1.调用 V8 编程接口的例子和对应的内存管理方式:


第一条语句:表示建立一个域,用于包含一组 Handle 对象,便于管理和释放他们;


第二条语句:根据 Isolate 对象来获取一个 Context 对象,使用 Handle 来管理。Handle 对象本身存放在栈上,而实际的 Context 对象保存在堆中。


第三条语句:根据两个对象 Isolate 和 Context 来创建一个函数间使用的对象,使用 Persistent 类来管理;


第四条语句:表示为 Context 对象创建一个基于栈的域,下面的执行步骤都是在该域中对应的上下文中来进行的;


第五条语句:读入一段 JavaScript 代码;


第六条语句:将代码字符串编译成 V8 的内部表示,并保存成一个 Script 对象;


第七条语句:执行编译后的内部表示,获得生成的结果;

2.V8 的编译:


首先通过编译器将源代码编译成抽象语法树,不同于 JavaScriptCore 引擎,V8 引擎并不将抽象语法树转变成字节码,而是通过 JIT 编译器的全代码生成器从抽象语法树直接生成本地代码;


其过程中的主要类图如下:



Script:表示的是 JavaScript 代码,既包含源代码,又包含编译之后生成的本地代码,所以它既是编译入口,又是运行入口;


Compiter:编译器类,辅助 Script 类来编译生成代码,它主要起一个协调者的作用,会调用解析器(Parse)来生成抽象语法树和全代码生成器,来为抽象语法树生成本地代码;


Parse:将源代码解析并构建成抽象语法树,使用 AstNodeFactory 类来创建他们,并使用 Zone 类来分配内存;


AstNode:抽象语法树节点类,是其他所有节点的基类;


AstVisitor:抽象语法树的访问者类,主要用来遍历抽象语法树;


FullCodeGenerator:AstVisitor 类的子类,通过遍历抽象语法树来为 JavaScript 生成本地可执行的代码;

3.V8 运行

V8 运行阶段的主要类图如下:



Script:前面介绍过,包含编译之后生成的本地代码,运行代码的入口;


Execution:运行代码的辅助类,包含一些重要的函数“call”,它辅助进入和执行 Script 中的本地代码;


JSFunction:需要执行的 JavaScript 函数表示类;


Runtime:运行本地代码的辅助类,主要提供运行时各种辅助函数;


Heap:运行本地代码需要使用的内存堆;


MarkCompactCollector:垃圾回收机制的主要实现类,用来标记,清除和整理等基本的垃圾回收过程;


SweeperThread:负责垃圾回收的线程;


V8 中代码的执行过程如下图:


四.V8 引擎所做优化

1.优化回滚

Crankshaft 编译器主要针对热点函数进行优化,它是基于 JS 源码分析的,而不是本地代码。为了性能考虑 Crankshaft 编译器会进行一些乐观的预测,认为这些代码比较稳定,变量类型不会发生变化,所以能够生成高效的本地代码;然而进行优化之后,V8 发现并不是最优的,会执行优化回滚操作。

2.隐藏类

将对象划分成不同的组,相同的组内对象拥有相同的属性名和属性值,组内的所有对象贡献该信息;



实例中对象 a 和 b 包含相同的属性名,V8 就会把他们归为同一个组,也就是隐藏类;这些属性在隐藏类中有相同的偏移值,这样,对象 a 和 b 可以共享这个类型信息,当访问这些对象属性的时候,根据隐藏类的偏移值就可以知道他们的位置并进行访问。


###3.内存管理


V8 使用堆来管理 JavaScript 使用的数据,以及生成的代码,哈希表等;为了更方便的实现垃圾回收,同很多虚拟机一样,V8 将堆分成三个部分,第一个是年轻分代,第二个是年老分代,第三个是大对象保留的空间。如下图:


4.快照(Snapshot)

V8 引擎开始启动的时候,需要加载很多内置的全局对象,同时也要建立内置的函数,比如 Array、String、Math 等;为了让引擎更加整洁,加载对象与建立函数等任务都是使用 JS 文件来实现的,V8 引擎负责在编译和执行输入的 JavaScript 代码之前,先加载他们;


快照机制就是将一些内置的对象和函数加载之后的内存保存并序列化;序列化之后的结果很容易被发序列化,经过快照机制的启动时间,可以缩短启动时间;快照机制也能够将开发者认为需要的 JS 文件序列化,减少以后处理的时间;

5.绑定和扩展

V8 提供两种机制来扩展引擎的能力,第一是 Extension 机制,就是通过 V8 提供的基类 Extension 来达到扩展 JavaScript 能力的目的;第二是绑定,使用 IDL 文件或者接口文件来生成绑定文件,然后将这些文件同 V8 引擎代码一起编译。

五.实践 – 写 JavaScript 需要注意地方

1.不要破坏隐藏类


建议:在构造函数中初始化所有对象成员,不要在以后更改类型;以相同的顺序初始化对象成员。

2.数据表示

在 V8 中,数据的表示分成两个部分,第一个部分是数据的实际内容,他们是变长的,第二部分是数据的句柄,句柄的大小是固定的,句柄中包含指向数据的指针。为什么要这样设计呢?主要是因为 V8 需要进行垃圾回收,并需要移动这些数据内容,如果直接使用指针的话就会出问题或者需要比较大的开销,使用句柄的话就不存在这些问题,只需要将句柄中的指针修改即可。


具体的定义如下:



一个 Handler 的大小是 4 字节(32 位机器),整数直接从 value_中获取值,而无需从堆中分配,然后分配一个指针指向它,这可以减少内存的使用并增加数据的访问速度。


所以:对于数值来说,只要能够使用整数的,尽量不要使用浮点数。

3.数组初始化


建议:


初始化使用数组常量小型固定大小的数组


不要储存在数字数组非数字值(对象)


不要删除数组中的元素,尤其是数字数组


不要装入未初始化或删除元素

4.内存

对引用不再使用的对象的变量设置为空(a = null),引入 delete 关键字,删除无用对象。

5.优化回滚

不要书写出触发优化回滚的代码,否则会大幅降低代码的性能;执行多次之后,不要出现修改对象类型的语句。


本文转载自公众号小时光茶舍(ID:gh_7322a0f167b5)。


原文链接:


https://mp.weixin.qq.com/s/nV75KBmRGpcE7Pn5Z-HUFg


2019-08-22 10:502270

评论

发布
暂无评论
发现更多内容

国产容器技术,实现小程序在任何应用中运行

Geek_2305a8

Qwen2.5 大语言模型特点解析

Botnow

AI 大语言模型 LLMOps AI 智能体 Qwen2.5

9月20日,Bonree ONE 3.0 产品发布会北京站即将开启!

博睿数据

详细介绍陪玩系统如何打包为APP小程序H5公众号

多客潇潇

全面拥抱生成式AI时代 F5 AppWorld全球巡演中国站盛大启幕

科技热闻

避免高额开发费用,如何轻松开发类似喜马拉雅的听书平台?

软件开发-梦幻运营部

多协议数据库管理工具:Navicat Premium (Win&Mac) 中文激活版

你的猪会飞吗

Navicat Premium Navicat Premium 16 Navicat Premium 中文版

Cloud Kernel SIG 月度动态:ANCK 新增多家厂商硬件新特性支持

OpenAnolis小助手

开源 操作系统 龙蜥社区 龙蜥sig

软件系统反脆弱指南

FunTester

签署《AI安全国际对话威尼斯共识》 智源持续推动人工智能安全发展

智源研究院

智启未来!和鲸联合南开大学赵宏教授,共论 AI 挑战下的教育教学新理念与新方法

ModelWhale

Python 人工智能 高等教育

高性能连接池之HikariCP框架分析:高性能逐条分解(架构师篇)

肖哥弹架构

Java HikariCP 连接池

AI 场景下如何构建运维的标准化能力?SOMA 智能运维计划发布 | 2024 龙蜥大会

OpenAnolis小助手

开源 操作系统 系统运维 龙蜥社区 龙蜥社区系统运维联盟

Difference analysis of IPQ4029 and IPQ4028 WiFi 5 chips

wifi6-yiyi

IPQ4019 wifi5

全面掌握 Jest:从零开始的测试指南(下篇)

EquatorCoco

前端

京东短网址高可用提升最佳实践

京东科技开发者

京东云JoyCoder荣获AI4SE“银弹”优秀案例

京东科技开发者

AI 镜像云市场伙伴招募计划发布!服务商闭门会精彩回顾 | 2024 龙蜥大会

OpenAnolis小助手

开源 操作系统 龙蜥社区 AI 镜像

阿里 Qwen2.5 开源发布;YouTubeVeo 引入 Google DeepMind Veo 模型丨 RTE 开发者日报

声网

恭喜!龙蜥社区2024年中三大奖项评选名单新鲜出炉

OpenAnolis小助手

开源 操作系统 龙蜥社区

专访浪潮信息:AI 原生时代,浪潮信息引领服务器操作系统创新 全面贡献龙蜥社区

OpenAnolis小助手

开源 操作系统 龙蜥社区 龙蜥操作系统大会

专访阿里云:AI 时代服务器操作系统洗牌在即,生态合作重构未来

OpenAnolis小助手

开源 AI 操作系统 龙蜥社区 龙蜥操作系统大会

简化插件的添加和更新流程

NocoBase

开源 低代码 无代码 版本更新

强大的终端SSH工具:SecureCRT (Win&Mac) 激活版

你的猪会飞吗

SecureCRT下载 SecureCRT mac SecureCRT Mac破解版 SecureCRT 安装教程

IPQ4019|Why Choose DR4019 SOM for Your Next WiFi Project? Dual-Band, USB 3.0, and OpenWRT

wallyslilly

诚邀见证2024九章云极DataCanvas算力包产品发布会!

九章云极DataCanvas

专访AMD:AMD 正式加入龙蜥社区首秀:开源协作与 AI 创新的交汇点

OpenAnolis小助手

开源 AI 操作系统 龙蜥社区

万界星空科技MES系统车间设备管理模块的功能

万界星空科技

工业互联网 mes 设备管理 万界星空科技 生产设备管理

js基础之setTimeout与setInterval原理分析

京东科技开发者

行业报告:仅百度文心智能体平台实现帮开发者赚钱

Geek_2d6073

Spring Boot 整合 MyBatis 的详细步骤(两种方式)

不在线第一只蜗牛

Spring Boot 后端

JavaScript引擎分析_文化 & 方法_厉心刚_InfoQ精选文章