写点什么

轻量版 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:033499

评论

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

【SSM】Mybatis系列——分页、使用注解开发、mybatis执行流程

胖虎不秃头

mybatis SSM框架 9月月更

数字孪生智慧校园三维可视化管理系统解决方案

数据可视化平台

智慧校园 智慧学校 智慧校园解决方案 智慧校园管理系统 校园三维可视化

和我一起入JavaScript

楠羽

JavaScript 笔记 知识 9月月更

给网站加个速,原来很简单!

sofiya

【SSM】Spring系列——Spring概述、第一个Spring程序、容器接口和实现类

胖虎不秃头

spring ssm 9月月更

主流开源APM:Zipkin/Pinpoint/SkyWalking全面对比

穿过生命散发芬芳

APM 9月月更

官宣 | 极狐(GitLab) 公司成立一年完成 4 轮融资,夯实中国开源市场信心

极狐GitLab

开源 DevOps 融资 自主可控 极狐GitLab

select多路选择

飞翔

Go

一文读懂,硬核 Apache DolphinScheduler3.0 源码解析

白鲸开源

源码阅读 Apache DolphinScheduler 工作流编排 大数据 开源 大数据调度

企业在SaaS时代如何玩转帮助中心?

Baklib

【spring-kafka】@KafkaListener详解与使用

石臻臻的杂货铺

kafka 9月月更

经验分享|企业搭建帮助中心步骤

Baklib

降本增效的利器——组件化开发

力软低代码开发平台

MediaTek MT7915 Module 2T2R DR7915/Wallys Wi-Fi 6 Wave 1+ chipset

wallys-wifi6

MT7975 MT7915

给网站加个速,原来很简单

科技怪咖

BNBChain NFTScan 与 SpaceID 达成合作,在浏览器内支持 .bnb 域名搜索!

NFT Research

区块链 域名 bnb

降本增效两不误——云原生赋能航空业数字化转型

York

容器 云原生 数字化转型 开发运维 智慧航空

Java 设置 Word 中的段落缩进方式

Geek_249eec

Java word 段落缩进

如何写成高性能的代码(一):巧用Canvas绘制电子表格

葡萄城技术团队

html 前端 canvas html2canvas 纯前端表格技术

深度学习+大规模计算+大数据,谁才是未来的算力之王

Finovy Cloud

人工智能 云渲染

精讲数据归档分析 |Data Infra 研究社第四期

Databend

大数据 开源 活动预告 #开源 数据归档

专家亮相华为云快成长直播间云安全专场,“未雨绸缪”化解数据风险

创意时空

TiDB Hackathon 2022丨总奖金池超 35 万!邀你唤醒代码世界的更多可能性!

TiDB 社区干货传送门

黑客马拉松

云原生数据库前世今生

亚马逊云科技 (Amazon Web Services)

数据库 云原生

资深专家亮相华为云快成长直播间CDN专场,助力企业体验升级!

神奇视野

云原生数据库 Amazon DynamoDB 十年创新回顾

亚马逊云科技 (Amazon Web Services)

数据库 云原生

企业做好知识管理的方法:文档管理

Baklib

腾讯云Crane获国家级科技奖,助力企业降本增效节能减排

科技热闻

【SSM】Mybatis系列——多对一和一对多的处理、动态SQL

胖虎不秃头

mybatis SSM框架 9月月更

C站专家圈分享-低代码构建WebAPI的原理与体验

葡萄城技术团队

架构 低代码 开发 WebApi 前后端

直播预告 | 在 CurveBS 上部署跨机 PolarDB for PostgreSQL 集群

阿里云数据库开源

数据库 postgresql 阿里云 开源 polarDB

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