HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

轻量版 V8 最新优化成果:内存开销平均减少 22%

  • 2019-09-17
  • 本文字数:4022 字

    阅读完需:约 13 分钟

轻量版V8最新优化成果:内存开销平均减少22%

我们在 2018 年末启动了一个名为 V8 Lite 的项目,项目的目标在于大幅降低 V8 引擎的内存使用。这个项目原本被设想为 V8 的独立轻量版本,专门为低内存的移动或嵌入式设备设计。在这种场景中我们更在乎减少内存的使用,而不是执行的吞吐量。不过,在工作进行过程中我们意识到,我们为 Lite 模式所做的许多内存优化工作可以被移植到常规版本的 V8 引擎,从而使所有 V8 用户收益。


这篇文章重点介绍了项目中的关键优化策略,以及这些优化如何在实际中节省工作负载内存。


注意:如果你更喜欢看演讲而不是阅读文章,请欣赏这个视频

轻量模式

为了优化 V8 的内存使用,首先需要了解 V8 如何使用内存,以及中哪些对象类存在大比例的堆占用。我们使用 V8 的内存可视化工具分析了许多典型网页,追踪其中堆大小的使用情况。



V8 引擎加载印度时报网站时不同对象类型的堆占用情况


分析得知,V8 的堆很大一部分被非关键对象占用,这些对象专门用于优化 JavaScript 的执行和异常处理。比如:优化代码结构;用于确定如何优化代码的反馈类型;C++ 和 JavaScript 对象绑定需要的冗余元数据;堆栈符号化过程中需要的元数据;只在页面加载期间执行了几次的函数的字节码。


得到以上结果后,我们开始研究 V8 的轻量模式。主要策略是:以降低 JavaScript 的执行速度为代价,大幅减少可选对象的分配量,从而达到节省内存使用的目标。



许多轻量模式的改动可以通过配置现有的 V8 引擎达到目的,比如禁用 TurboFan 优化编译器。但其他的改动需要针对 V8 本身进行修改。


值得注意的是,既然轻量模式不需要优化代码,V8 就可以不再收集优化编译器所需的反馈类型。当 Ignition 解释器执行代码时,V8 会收集各种运算(如 + 或 o.foo)中传递的操作数的反馈类型,以便在稍后中进行优化。此类信息储存在反馈向量中,这些向量占用了堆内存很大一部分。轻量模式可以避免分配反馈向量,但解释器与 V8 的内联缓存基础架构期望来自于反馈向量的值,因此 V8 需要相当多的代码重构才能支持移除反馈向量的执行。


轻量模式在 v7.3 的 V8 版本中推出。与 v7.1 版本相比,通过禁用代码优化、禁止分配反馈向量、老化极少执行的代码(后文中会介绍),新版本在典型网页场景中减少了 22%的栈内存使用。对于明确希望以牺牲性能为代价以获得更好的内存使用率的应用程序而言,这是个好结果。但在完成这项工作的过程中,我们意识到可以通过把 V8 变“lazy”,在不影响性能的情况下实现轻量模式,以得到节省内存的结果。

惰性反馈向量分配

完全禁用反馈向量分配不仅阻止了 V8 TurboFan 编译器为代码进行优化,还阻止 V8 为常见的运算做内联缓存,比如 Ignition 解释器对于对象属性的加载。这会造成 V8 的执行时间显著回归,页面加载时间减少 12%,同时使典型交互式网页场景中 V8 的 CPU 使用时间增加 120%。


为了在没有性能回归的情况下,将大部分节省内存的提升效果带到常规版本的 V8 版本,我们采用了一种新的策略:在函数执行了一定量字节码的代码(目前为 1KB)后延迟地分配反馈向量。由于大多数函数不经常执行,因此惰性策略可以在大多数情况下避免反馈向量的分配,但仍然会在需要的地方快速分配它们,让代码得到优化的同时也避免了性能回归问题。


这种方法还有一个复杂因素与反馈向量组成的树有关。在这个树中,内嵌函数的反馈向量被储存为外部函数反馈向量的条目。这是因为新创建的函数闭包需要与同一个函数创建的所有其他闭包获得相同的反馈向量数组。由于反馈向量被延迟分配,我们不再能使用反馈向量生成此树,因为无法保证内部函数分配反馈向量时,外部函数已经分配好了反馈向量。为了解决这个问题,我们为函数创建了新的 ClosureFeedbackCellArray 结构维护这个树。当函数被调用时,代表它的 ClosureFeedbackCellArray 将会与一个完整的 FeedbackVector 交换。



反馈向量树在惰性分配前后的比对


实验结果与实际用户反馈表明,惰性反馈在桌面端没有性能回归问题。而在移动平台上,由于垃圾回收的减少,我们在某些低端设备中同样观察到了性能提升。因此,我们已经在包含轻量模式的所有 V8 版本中启用了惰性反馈分配。与最初不做惰性反馈的轻量模式,新方法有一些轻微的内存提升,不过这个代价可以被实际性能提升补偿。

惰性源码定位

在 JavaScript 编译为字节码后,源码定位表会随之生成,它记录了字节码序列对应的 JavaScript 源码中字符的位置信息。但源码定位表只会在符号化异常或者执行如程序调试等开发人员任务时才需要此信息,因此很少会被使用。


为了避免这种浪费,现在 V8 编译源码为字节码时不再收集源码的位置信息(假如没有附属调试器或分析器)。源码位置的收集仅发生在生成堆栈跟踪时,比如调用 Error.stack 方法,或者将异常堆栈跟踪打印到控制台时。这的确有一些成本,因为源码定位需要重新分析和编译函数,但是大多数网站并不会在生产中符号化堆栈跟踪,因此这不会产生任何可感知到的性能影响。


这个策略带来的一个新的挑战。我们需要找到一个生成可重复字节码的方法,不过这在之前是不做保证的。如果 V8 在收集源码定位时生成的字节码与原始代码不同,则产生的定位信息无法使字节码与源码对齐,并且堆栈跟踪可能指向源码中错误的位置。


某些情况下,由于某些解析器信息在函数初始预解析和之后的懒编译之间丢失,V8 可能会生成不同的字节码,这取决于函数是预编译还是懒编译。这些不匹配大多数是无害的,例如丢失掉不可变变量的跟踪信息,导致无法优化这类代码。但这种不匹配的事实,确实揭露了这项优化某些可能导致代码错误执行的潜在问题。因此,我们修复了这些不匹配问题,增加了额外的检查和压力模式,以确保函数预编译与懒编译总是产生一致的输出,让我们对 V8 解析器和预解释器的正确性和一致性有了更多信心。

字节码冲刷

JavaScript 源码编译产生的字节码会占用大量 V8 的堆内存,通常占约 15%,其中包含相关代码的元数据。许多函数只在初始化阶段执行,或者编译后很少再使用。


因此,我们增加了一个新特性,假如某个函数最近一段时间没有执行过,那么将会在垃圾回收期间将其编译的字节码冲刷掉。为了达成这个目标,我们为函数的字节码增加了老化标记:在每个主要(mark-compact)的垃圾回收期间增加老化数值,并在执行函数的时候将数值重置为零。任何超过老化阈值的字节码都可能在下一次的垃圾回收期间回收。如果字节码被回收后函数再次被执行,那么它将会被重新编译。


确保字节码只有真正不再被需要的时候冲刷是个技术挑战。例如,如果函数 A 调用了另一个长时间运行的函数 B,那么有可能函数 A 仍在调用栈上的时候被增加老化标记。在这种情况下,即使函数 A 达到了老化阈值我们也不想冲刷掉函数 A 的字节码,因为长时间运行的函数 B 在返回的时候需要继续执行函数 A。因此,字节码在达到寿命期限时被保留为弱链接,但如果函数引用存在于调用栈或者其他地方的时候,其字节码会保留为强链接。字节码在没有任何强链接的时候才会被冲刷。


除了冲刷字节码,V8 同时冲刷了这些函数相关的反馈向量。不过,冲刷字节码的内存回收周期内没法同时冲刷反馈向量,这是因为它们由同不同的对象保持。字节码由本机上下文独立的 SharedFunctionInfo 保持,而反馈向量由本机上下文依赖的 JSFunction 保持。因此,反馈向量会在随后的垃圾回收循环中冲刷。



图为一个老化函数在两个 GC 循环后的对象布局

其他优化策略

除了上面提到的主要优化点之外,我们还发现并解决了两个引发效率低下的问题。


一个是降低了 FunctionTemplateInfo 对象的大小。这些对象储存了与 FunctionTemplate 有关的内部元数据,使得 Chrome 等 V8 的嵌入程序可以在 JavaScript 中访问以 C++ 实现的函数回调。为了实现 DOM Web API, Chrome 引入了许多 FunctionTemplate 对象,而这些对象会增加 V8 的堆占用。在分析了 FunctionTemplate 的典型用法后,我们发现 FunctionTemplateInfo 对象的 11 个字段中,通常只有 3 个字段被修改为非默认值。因此,我们拆分了 FunctionTemplateInfo 对象,使得很少使用的字段储存在一个只会按需分配的副表中。


另一个与如何反优化 TurboFan 产生的优化代码有关。由于 TurboFan 会进行推测优化,如果某些优化条件不再成立,那么可能就需要回退(反优化)到使用解释器执行。每个反优化点都有一个 ID,帮助运行时决定解释器返回到字节码哪个执行位置。优化前,此 ID 通过将优化后的代码位置跳转到大型跳转表的一个特定位置计算,这个跳转表会载入正确的 ID 到寄存器中,然后跳转到运行时去处理反优化。这样做的好处是是优化代码的每个反优化点只需一个跳转指令。然而反优化跳转表是预先分配的,并且必须足够大到支持整个反优化 ID 范围。我们现在选择修改 TurboFan 的代码,使得优化代码的反优化点在调用运行时之前直接加载反优化 ID。这样我们就可以移除整个大型跳转表,代价是优化后的代码大小略有增加。

结果

以上是我们在最近七个版本的 V8 引擎中做的优化。通常来说,这些优化首先引入到轻量模式里,随后被带到 V8 的默认配置。



图为一组典型网页在 AndroidGo 设备上的平均堆大小



图为 V8 引擎 v7.8 (Chrome 78) 与 v7.1 (Chrome 7.1) 的单页内存节省对比差异


在此期间,从一系列经典网站上的结果来看,V8 的堆大小的堆大小平均减小了 18%。这对低端的 AndroidGo 移动设备来说,平均内存占用减少了 1.5 MB。这些优化没有对性能基准测试产生重大影响,同时也在实际网页交互中经受住了考验。


通过禁用函数优化,轻量模式可以以 JavaScript 执行吞吐量为代价进一步节省内存。平均来说,轻量模式可以为设备节省 22%的内存,甚至在有些页面可以节省到高达 32%。这对于 AndroidGo 设备来说相当于 1.8 MB 的 V8 堆内存。



图为 V8 引擎 v7.8 (Chrome 78) 与 v7.1 (Chrome 7.1) 的内存节省对比差异


将每个优化点独立拆分来看时,容易看出不同页面从这些优化中得到了不同比例的优化提升。未来我们将继续探索潜在的优化策略,在保证 JavaScript 快速执行的情况下,进一步降低 V8 内存使用率。


原文:https://v8.dev/blog/v8-lite


2019-09-17 11:033295

评论

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

fastposter 2.5.0 全新发布 一款电商级海报生成器

物有本末

Java Python 海报 海报生成器

netty系列之:NIO和netty详解

程序那些事

Java Netty 程序那些事 2月月更

【邀请函】3月4日平台赋能 数智创新 ———用友BIP PaaS云平台iuap数智化百城论坛·济南站

用友BIP

用友 用友iuap 企业数智化 平台赋能 数智创新

丰e足食将大量引入算法人才 加快无人零售算法研发和应用

江湖老铁

ShardingSphere 助力当当 WMS:订单效率提升 30%、节约成本上千万

SphereEx

数据库 开源 ShardingSphere wms SphereEx

一句话回顾会

Bruce Talk

敏捷 Agile 回顾会 Coach/Facilitate

2022年中国数字孪生城市市场分析:孪生城市产业经济全域协作

易观分析

数字孪生

千万级车联网 MQTT 消息平台架构设计|车联网平台搭建从入门到精通 02

EMQ映云科技

架构 车联网 物联网 mqtt 分布式消息流平台

中台和低代码,“零和”还是“竞合”?

WorkPlus

docker 批量删除 none 镜像

AlwaysBeta

Docker 容器 镜像 docker image docker build

数据治理:从一把手工程到数据文化!

用友BIP

数据治理 用友 用友iuap 数据文化

3月2日,阿里云开源 PolarDB 企业级架构将迎来重磅发布

阿里云数据库开源

数据库 阿里云 开源 分布式 polarDB

RadonDB MySQL on K8s 2.1.2 发布!

RadonDB

MySQL 数据库 高可用 RadonDB KubeSphere

亚信科技AntDB数据库参与“国内首款”事务型性能测试工具开源发布会,树立金融技术风向标

亚信AntDB数据库

AntDB 性能基准测试 中国信通院

那一年,我们在巴塞罗那找到的「ONES 图腾」

万事ONES

ONES

TDengine在蔚来能源系统的落地实践

TDengine

MySQL 数据库 tdengine 物联网 时序数据库

开发运维效率提升 80%,计算成本下降 50%,分众传媒的 Serverless 实践

阿里巴巴云原生

阿里云 Serverless 云原生 合作案例

哈佛商业评论对话王文京:如何制定正确的数智化战略和路径?

用友BIP

用友 数智化

低代码实现探索(三十二)多版本开发/本地开发

零道云-混合式低代码平台

大数据开发join的运行原理_大数据培训

@零度

hive 大数据开发

开发之痛:稳定的测试环境,怎么就那么难 | 研发效能提升36计

阿里云云效

云计算 阿里云 DevOps 云原生 测试

中国信息通信研究院云计算与大数据研究所一行莅临亚信科技考察交流

亚信AntDB数据库

元年云李彤:ToB产品应具备数据驱动和「宽能力」

ToB行业头条

web前端开发nodejs基本原理_前端培训

@零度

node.js 前端开发

全链路灰度这样做,新需求迭代上线也能放心干饭

阿里巴巴云原生

阿里云 微服务 云原生 灰度

加速企业数据应用创新的核心能力——灵活性

用友BIP

数据中台 创新 用友

Khronos 会议干货 | WebGPU 1.0 即将发布

Orillusion

开源 WebGL 元宇宙 Metaverse webgpu

私有模块上线,用它来开发外包项目,真香!

ModStart开源

【Python】第二章(条件语句和循环语句)

謓泽

Python 2月月更

什么是数字化转型?

WorkPlus

linux服务器是什么?如何快捷安全管理?

行云管家

运维 服务器

轻量版V8最新优化成果:内存开销平均减少22%_语言 & 开发_Mythri Alle_InfoQ精选文章