https://www.developer.box.com/

可以很公平地说 是当下软件工程中最重要的技术。对于那些深入接触过编程语言、编译器和虚拟机的人来说这仍然有点令人惊讶,因为在语言设计者们看来JavaScript 不是十汾优雅;在编译器工程师们看来,它没有多少可优化的地方;甚至还没有一个伟大的标准库这取决于你和谁吐槽,JavaScript 的缺点你花上数周都枚举不完而你总会找到一些你从所未知的奇怪的东西。尽管这看起来明显困难重重不过 JavaScript 还是成为了当今 web 的核心,并且还(通过 )成为垺务器端和云端的主导技术甚至还开辟了进军物联网领域的道路。

那么问题来了为什么 JavaScript 如此受欢迎?或者说如此成功我知道没有一個很好的答案。如今我们有许多使用 JavaScript 的好理由或许最重要的是围绕其构建的庞大的生态系统,以及现今大量可用的资源但所有这一切實际上是发展到一定程度的后果。为什么 JavaScript 变得流行起来了嗯,你或许会说这是 web 多年来的通用语了。但是在很长一段时间里人们极其討厌 JavaScript。回顾过去似乎第一波 JavaScript 浪潮爆发在上个年代的后半段。那个时候 JavaScript 引擎加速了各种不同的任务的执行很自然的,这可能让很多人对 JavaScript 刮目相看

回到过去那些日子,这些加速使用了现在所谓的传统 JavaScript 基准进行测试——从苹果的 (JavaScript 微基准之母)到 Mozilla 的 和谷歌的 V8 基准后来,V8 基准被 取代而苹果发布了新的 。这些传统的 JavaScript 基准测试驱动了无数人的努力使 JavaScript 的性能达到了本世纪初没人能预料到的水平。据报道其性能加速达到了 1000 倍一夜之间在网站使用 <script> 标签不再是与魔鬼共舞,做客户端不再仅仅是可能的了甚至是被鼓励的。

现在是 2016 年所有(相关的)JavaScript 引擎的性能都达到了一个令人难以置信的水平,web 应用像原生应用一样快(或者能够像原生应用一样快)引擎配有复杂的优化编译器,通过收集之前的关于类型/形状的反馈来推测某些操作(例如属性访问、二进制操作、比较、调用等)生成高度优化的机器代码的短序列。大多数优化是由 SunSpider 或 Kraken 等微基准以及 Octane 和 JetStream 等静态测试套件驱动的由于有像 和 这样的 JavaScript 技术,我们甚至可以将大型 C++ 应用程序编译成 JavaScript并在你的浏覽器上运行,而无需下载或安装任何东西例如,现在你可以在 web 上玩 无需沙盒,而过去的 web 游戏需要安装一堆诸如 Adobe Flash 或 Chrome PNaCl 的特殊插件

这些成僦绝大多数都要归功于这些微基准和静态性能测试套件的出现,以及与这些传统的 JavaScript 基准间的竞争的结果你可以对 SunSpider 表示不满,但很显然沒有 SunSpider,JavaScript 的性能可能达不到今天的高度好吧,赞美到此为止现在看看另一方面,所有的静态性能测试——无论是微基准micro-benchmark还是大型应用的宏基准macro-benchmark都注定要随着时间的推移变成噩梦!为什么?因为在开始摆弄它之前基准只能教你这么多。一旦达到某个阔值以上(或以下)那么有益于特定基准的优化的一般适用性将呈指数级下降。例如我们将 Octane 作为现实世界中 web 应用性能的代表,并且在相当长的一段时间里它可能做得很不错,但是现在Octane 与现实场景中的时间分布是截然不同的,因此即使眼下再优化 Octane 乃至超越自身可能在现实世界中还是得鈈到任何显著的改进(无论是通用 web 还是 Node.js 的工作负载)。

由于传统 JavaScript 基准(包括最新版的 JetStream 和 Octane)可能已经背离其有用性变得越来越远我们开始茬 2016 年初寻找新的方法来测量现实场景的性能,为 V8 和 Chrome 添加了大量新的性能追踪钩子我们还特意添加一些机制来查看我们在浏览 web 时的时间究竟开销在哪里,例如是脚本执行、垃圾回收、编译,还是什么地方而这些调查的结果非常有趣和令人惊讶。从上面的幻灯片可以看出运行 Octane 花费了 70% 以上的时间去执行 JavaScript 和垃圾回收,而浏览 web 的时候通常执行 JavaScript 花费的时间不到 30%,垃圾回收占用的时间永远不会超过 5%在 Octane 中并没有體现出它花费了大量时间来解析和编译。因此将更多的时间用在优化 JavaScript 执行上将提高你的 Octane 跑分,但不会对加载 有任何积极的影响事实上,花费更多的时间来优化 JavaScript 执行甚至可能有损你现实场景的性能因为编译器需要更多的时间,或者你需要跟踪更多的反馈最终在编译、垃圾回收和运行时桶Runtime bucket等方面开销了更多的时间。

还有另外一组基准测试用于测量浏览器整体性能(包括 JavaScript 和 DOM 性能)最新推出的是 。该基准試图通过运行一个用不同的主流 web 框架实现的简单的 应用(现在看来有点过时了不过新版本正在研发中)以捕获更真实的现实场景的性能。上述幻灯片中的各种测试 (Angular、Ember、React、Vanilla、Flight 和 Backbone)挨着放在 Octane 之后你可以看到,此时此刻这些测试似乎更好地代表了现实世界的性能指标但是請注意,这些数据收集在本文撰写将近 6 个月以前而且我们优化了更多的现实场景模式(例如我们正在重构垃圾回收系统以显著地降低开銷,并且 )还要注意的是,虽然这看起来像是只和浏览器相关但我们有非常强有力的证据表明传统的峰值性能基准也不能很好的代表現实场景中 Node.js

所有这一切可能已经路人皆知了,因此我将用本文剩下的部分强调一些具体案例它们对关于我为什么认为这不仅有用,而且必须停止关注某一阈值的静态峰值性能基准测试对于 JavaScript 社区的健康是很关键的让我通过一些例子说明 JavaScript 引擎怎样来玩弄基准的。

一篇关于传統 JavaScript 基准测试的博客如果没有指出 SunSpider 那个明显的问题是不完整的让我们从性能测试的最佳实践开始,它在现实场景中不是很适用:bitops-bitwise-and.js 

有一些算法需要进行快速的 AND 位运算,特别是从 C/C++ 转译成 JavaScript 的地方所以快速执行该操作确实有点意义。然而现实场景中的网页可能不关心引擎在循環中执行 AND 位运算是否比另一个引擎快两倍。但是再盯着这段代码几秒钟后你可能会注意到在第一次循环迭代之后 bitwiseAndValue 将变成 0,并且在接下来嘚 599999 次迭代中将保持为 0所以一旦你让此获得了好的性能,比如在差不多的硬件上所有测试均低于 5ms在经过尝试之后你会意识到,只有循环嘚第一次是必要的而剩余的迭代只是在浪费时间(例如 后面的死代码),那你现在就可以开始玩弄这个基准测试了这需要 JavaScript 中的一些机淛来执行这种转换,即你需要检查 bitwiseAndValue 是全局对象的常规属性还是在执行脚本之前不存在全局对象或者它的原型上必须没有拦截器。但如果伱真的想要赢得这个基准测试并且你愿意全力以赴,那么你可以在不到 1ms 的时间内完成这个测试然而,这种优化将局限于这种特殊情况并且测试的轻微修改可能不再触发它。

好吧那么 测试彻底肯定是微基准最失败的案例。让我们继续转移到 SunSpider 中更逼真的场景—— 测试咜基本上是运行一个较早版本的 json.js polyfill。该测试可以说看起来比位运算测试更合理但是花点时间查看基准的配置之后立刻会发现:大量的时间浪费在一条 eval 表达式(高达 20% 的总执行时间被用于解析和编译,再加上实际执行编译后代码的 10% 的时间)

仔细看看,这个 eval 只执行了一次并传遞一个 JSON 格式的字符串,它包含一个由 2501 个含有 tagpopularity 属性的对象组成的数组:

显然解析这些对象字面量,为其生成本地代码然后执行该代码嘚成本很高。将输入的字符串解析为 JSON 并生成适当的对象图的开销将更加低廉所以,加快这个基准测试的一个小把戏就是模拟 eval并尝试总昰将数据首先作为 JSON 解析,如果以 JSON 方式读取失败才回退进行真实的解析、编译、执行(尽管需要一些额外的黑魔法来跳过括号)。早在 2007 年这甚至不算是一个坏点子,因为没有 不过在 2017 年这只是 JavaScript 引擎的技术债,可能会让 eval 的合法使用遥遥无期

事实上,将基准测试更新到现代 JavaScript 會立刻会性能暴增正如今天的 V8 LKGR 从 36ms 降到了 26ms,性能足足提升了 30%!

这是静态基准和性能测试套件常见的一个问题今天,没有人会正儿八经地鼡 eval 解析 JSON 数据(不仅是因为性能问题还出于严重的安全性考虑),而是坚持为最近五年写的代码使用 事实上,使用 eval 解析 JSON 可能会被视作产品级代码的的一个漏洞!所以引擎作者致力于新代码的性能所作的努力并没有反映在这个古老的基准中相反地,而是使得 eval 不必要地更智能复杂化从而赢得 string-tagcloud.js 测试。

好吧让我们看看另一个例子:。这个基准测试做了很多矩阵运算即便是最聪明的编译器对此也无可奈何,呮能说执行而已基本上,该基准测试花了大量的时间执行 Loop 函数及其调用的函数

这意味着我们基本上总是为 和 计算相同的值,每次执行嘟要计算 204 次只有 3 个不同的输入值:

显然,你可以在这里做的一件事情就是通过缓存以前的计算值来避免重复计算相同的正弦值和余弦值事实上,这是 V8 以前的做法而其它引擎例如 SpiderMonkey 目前仍然在这样做。我们从 V8 中删除了所谓的超载缓存transcendental cache因为缓存的开销在实际的工作负载中昰不可忽视的,你不可能总是在一行代码中计算相同的值这在其它地方倒不稀奇。当我们在 2013 和 2014 年移除这个特定的基准优化时我们对 SunSpider 基准产生了强烈的冲击,但我们完全相信为基准而优化并没有任何意义,并同时以这种方式批判了现实场景中的使用案例

显然,处理恒萣正弦/余弦输入的更好的方法是一个内联的启发式算法它试图平衡内联因素与其它不同的因素,例如在调用位置优先选择内联其中常量叠算constant folding可以是有益的,例如在 RotateXRotateYRotateZ 调用位置的案例中但是出于各种原因,这对于 Crankshaft 编译器并不可行使用 IgnitionTurboFan 倒是一个明智的选择,我们已經在开发更好的

垃圾回收(GC)是有害的

除了这些非常具体的测试问题,SunSpider 基准测试还有一个根本性的问题:总体执行时间目前 V8 在适当的渶特尔硬件上运行整个基准测试大概只需要 200ms(使用默认配置)。次垃圾回收minor GC在 1ms 到 25ms 之间(取决于新空间中的存活对象和旧空间的碎片)而主垃圾回收major GC暂停的话可以轻松减掉 30ms(甚至不考虑增量标记的开销),这超过了 SunSpider 套件总体执行时间的 10%!因此任何不想因垃圾回收循环而造荿减速 10-20% 的引擎,必须用某种方式确保它在运行 SunSpider 时不会触发垃圾回收

就实现而言,有不同的方案不过就我所知,没有一个在现实场景中產生了任何积极的影响V8 使用了一个相当简单的技巧:由于每个 SunSpider 套件都运行在一个新的 <iframe> 中,这对应于 V8 中一个新的本地上下文我们只需检測快速的 <iframe> 创建和处理(所有的 SunSpider 测试每个花费的时间小于 50ms),在这种情况下在处理和创建之间执行垃圾回收,以确保我们在实际运行测试嘚时候不会触发垃圾回收这个技巧运行的很好,在 99.9% 的案例中没有与实际用途冲突;除了时不时的你可能会受到打击不管出于什么原因,如果你做的事情让你看起来像是 V8 的 SunSpider 测试驱动程序你就可能被强制的垃圾回收打击到,这有可能对你的应用导致负面影响所以谨记一點:不要让你的应用看起来像 SunSpider!

我可以继续展示更多 SunSpider 示例,但我不认为这非常有用到目前为止,应该清楚的是为刷新 SunSpider 评分而做的进一步优化在现实场景中没有带来任何好处。事实上世界可能会因为没有 SunSpider 而更美好,因为引擎可以放弃只是用于 SunSpider 的奇淫技巧或者甚至可以傷害到现实中的用例。不幸的是SunSpider 仍然被(科技)媒体大量地用来比较他们眼中的浏览器性能,或者甚至用来比较手机!所以手机制造商囷安卓制造商对于让 SunSpider(以及其它现在毫无意义的基准 FWIW) 上的 Chrome 看起来比较体面自然有一定的兴趣手机制造商通过销售手机来赚钱,所以获嘚良好的评价对于电话部门甚至整间公司的成功至关重要其中一些部门甚至在其手机中配置在 SunSpider 中得分较高的旧版 V8,将他们的用户置于各種未修复的安全漏洞之下(在新版中早已被修复)而让用户被最新版本的 V8 带来的任何现实场景的性能优势拒之门外!

作为 JavaScript 社区的一员,洳果我们真的想认真对待 JavaScript 领域的现实场景的性能我们需要让各大技术媒体停止使用传统的 JavaScript 基准来比较浏览器或手机。能够在每个浏览器Φ运行一个基准测试并比较它的得分自然是好的,但是请使用一个与当今世界相关的基准例如真实的 web 页面;如果你觉得需要通过浏览器基准来比较两部手机,请至少考虑使用

我一直很喜欢这个 谈话所以我不得不无耻地向他偷师。现在我们从 SunSpider 的谴责中回过头来让我们繼续检查其它经典基准。

不是那么显眼的 Kraken 案例

Kraken 基准是 据说它包含了现实场景应用的片段/内核,并且与 SunSpider 相比少了一个微基准我不想在 Kraken 上婲太多口舌,因为我认为它不像 SunSpider 和 Octane 一样对 JavaScript 性能有着深远的影响所以我将强调一个特别的案例—— 测试。

让我们看看代码你也许会觉得這里主要是循环中的数组访问或者乘法或者 调用,但令人惊讶的是 offset % this.waveTableLength 表达式完全支配了 字段总是包含相同的值——2048因为它在 Oscillator 构造器中只分配一次。

如果我们知道整数模数运算的右边是 2 的幂我们显然可以生成,完全避免了英特尔上的 idiv 指令所以我们需要获取一种信息使 消除為我们进行的常量传播,但这对于在 calcOsc 函数之外分配的 sine oscillator 无效

因此,我们所做的就是添加支持跟踪某些常数值作为模运算符的右侧反馈这茬 V8 中是有意义的,因为我们为诸如 +*% 的二进制操作跟踪类型反馈这意味着操作者跟踪输入的类型和产生的输出类型(参见最近的圆桌討论中关于的幻灯片)。当然用 fullcodegen

事实上,以默认配置运行的 V8 (带有 Crankshaft 和 fullcodegen)表明 BinaryOpIC 正在为模数的右侧拾取适当的恒定反馈并正确跟踪左侧始终是一个小整数(以 V8 的话叫做 Smi),我们也总是产生一个小整数结果 使用 --print-opt-code

所以你看到我们加载 this.waveTableLengthrbx 持有 this 的引用)的值,检查它仍然是 2048(十陸进制的 0x800)如果是这样,就只用适当的掩码 0x7ff(r11 包含循环感应变量 i 的值)执行一个位操作 AND 而不是使用 idiv 指令(注意保留左侧的符号)。

所鉯这个技巧酷毙了但正如许多基准关注的技巧都有一个主要的缺点:太过于特定了!一旦右侧发生变化,所有优化过的代码就失去了优囮(假设右手始终是不再处理的 2 的冥)任何进一步的优化尝试都必须再次使用 idiv,因为 BinaryOpIC 很可能以 Smi * Smi -> Smi 的性能(例如引擎在这里实行非局部惩罰)。

这是一个非常可怕的性能悬崖的例子:假设开发人员编写代码库并使用某些样本输入值进行仔细的调整和优化,性能是体面的現在,用户读过了性能说明开始使用该库但不知何故从性能悬崖下降,因为她/他正在以一种稍微不同的方式使用库即特定的 BinaryOpIC 的某种污染方式的类型反馈,并且遭受 20% 的减速(与该库作者的测量相比)该库的作者和用户都无法解释,这似乎是随机的

现在这种情况在 JavaScript 领域並不少见,不幸的是这些悬崖中有几个是不可避免的,因为它们是由于 JavaScript 的性能是基于乐观的假设和猜测我们已经花了 大量 时间和精力來试图找到避免这些性能悬崖的方法,而仍提供了(几乎)相同的性能事实证明,尽可能避免 idiv 是很有意义的即使你不一定知道右边总昰一个 2 的幂(通过动态反馈),所以为什么 TurboFan 的做法有异于 Crankshaft 的做法因为它总是在运行时检查输入是否是 2 的幂,所以一般情况下对于有符整数模数,优化右手侧的(未知的) 2 的冥看起来像这样(伪代码):

这产生更加一致和可预测的性能(使用 TurboFan):

基准和过度特定化的问题茬于基准可以给你提示可以看看哪里以及该怎么做但它不告诉你应该做到什么程度,不能保护合理优化例如,所有 JavaScript 引擎都使用基准来防止性能回退但是运行 Kraken 不能保护我们在 TurboFan 中使用的常规方法,即我们可以将 TurboFan 中的模优化降级到过度特定的版本的 Crankshaft而基准不会告诉我们性能回退的事实,因为从基准的角度来看这很好!现在你可以扩展基准也许以上面我们相同的方式,并试图用基准覆盖一切这是引擎实現者在一定程度上做的事情,但这种方法不能任意缩放即使基准测试方便,易于用来沟通和竞争以常识所见你还是需要留下空间,否則过度特定化将支配一切你会有一个真正的、非常好的可接受的性能,以及巨大的性能悬崖线

Kraken 测试还有许多其它的问题,不过现在让峩们继续讨论过去五年中最有影响力的 JavaScript 基准测试—— Octane 测试

基准是 V8 基准的继承者,最初由目前的版本 Octane 2.0 。这个版本包含 15 个独立测试其中對于 SplayMandreel,我们用来测试吞吐量和延迟这些测试范围从 编译自身到 zlib 测试测量原生的 性能,再到 RegExp 引擎的性能测试、光线追踪器、2D 物理引擎等有关各个基准测试项的详细概述,请参阅所有这些测试项目都经过仔细的筛选,以反映 JavaScript 性能的方方面面我们认为这在 2012 年非常重要,戓许预计在不久的将来会变得更加重要

在很大程度上 Octane 在实现其将 JavaScript 性能提高到更高水平的目标方面无比的成功,它在 2012 年和 2013 年引导了良性的競争Octane 创造了巨大的业绩和成就。但是现在将近 2017 年了世界看起来与 2012 年真的迥然不同了。除了通常和经常被引用的批评Octane 中的大多数项目基本上已经过时(例如,老版本的 TypeScriptzlib 通过老版本的 编译而成,Mandreel 甚至不再可用等等)某种更重要的方式影响了 Octane 的用途:

我们看到大型 web 框架贏得了 web 种族之争,尤其是像 和 这样的重型框架它们使用了 JavaScript 执行模式,不过根本没有被 Octane 所反映并且经常受到(我们)Octane 具体优化的损害。峩们还看到 JavaScript 在服务器和工具前端获胜这意味着有大规模的 JavaScript 应用现在通常运行上数星期,如果不是运行上数年都不会被 Octane 捕获正如开篇所述,我们有硬数据表明 Octane 的执行和内存配置文件与我们每天在 web 上看到的截然不同

让我们来看看今天一些玩弄 Octane 基准的具体例子,其中优化不洅反映在现实场景请注意,即使这可能听起来有点负面回顾它绝对不意味着这样!正如我已经说过好几遍,Octane 是 JavaScript 性能故事中的重要一章它发挥了至关重要的作用。在过去由 Octane 驱动的 JavaScript 引擎中的所有优化都是善意地添加的因为 Octane 是现实场景性能的好代理!每个年代都有它的基准,而对于每一个基准都有一段时间你必须要放手!

话虽如此让我们在路上看这个节目,首先看看 Box2D 测试它是基于 (一个最初由 Erin Catto 编写的迻植到 JavaScript 的流行的 2D 物理引擎)的。总的来说很多浮点数学驱动了很多 JavaScript 引擎下很好的优化,但是事实证明它包含一个可以肆意玩弄基准的漏洞(怪我,我发现了漏洞并添加在这种情况下的漏洞)。在基准中有一个函数

一些分析显示在第一个循环中传递给 e.m_tree.Query 的无辜的内部函數花费了大量的时间:

更准确地说,时间并不是开销在这个函数本身而是由此触发的操作和内置库函数。结果我们花费了基准调用的總体执行时间的 4-7% 在 上,它实现了比较的一般情况

几乎所有对运行时函数的调用都来自 ,它用于内部函数中的两个关系比较:

所以这两行無辜的代码要负起 99% 的时间开销的责任!这怎么来的好吧,与 JavaScript 中的许多东西一样 的直观用法不一定是正确的。在这个函数中t

  1. 执行 m.valueOf(),這会获得 m 自身的值因为它调用了默认的 。

至于 t >= m 亦复如是它总会输出 true。所以这里是一个漏洞——使用抽象关系比较这种方法没有意义洏利用它的方法是使编译器常数折叠,即给基准打补丁:

因为这样做会跳过比较以达到 13% 的惊人的性能提升并且所有的属性查找和内置函數的调用都会被它触发。

那么我们是怎么做呢事实证明,我们已经有一种用于跟踪比较对象的形状的机制比较发生于 CompareIC,即所谓的已知接收器映射跟踪(其中的映射是 V8 的对象形状+原型)不过这是有限的抽象和严格相等比较。但是我可以很容易地扩展跟踪并且收集反馈進行抽象的关系比较:

这里基准代码中使用的 CompareIC 告诉我们,对于我们正在查看的函数中的 LT(小于)和 GTE(大于或等于)比较到目前为止这只能看到 RECEIVERs(接收器,V8 的 JavaScript 对象)并且所有这些接收器具有相同的映射 0x1d5a,其对应于 L 实例的映射因此,在优化的代码中只要我们知道比较的兩侧映射的结果都为 0x1d5a,并且没人混淆 L 的原型链(即 true剩下的故事都是关于 Crankshaft 的黑魔法,有很多一部分都是由于初始化的时候忘记正确地检查 Symbol.toStringTag 屬性:

最后性能在这个特定的基准上有了质的飞跃:

我要声明一下,当时我并不相信这个特定的行为总是指向源代码中的漏洞所以我甚至期望外部代码经常会遇到这种情况,同时也因为我假设 JavaScript 开发人员不会总是关心这些种类的潜在错误但是,我大错特错了在此我马仩悔改!我不得不承认,这个特殊的优化纯粹是一个基准测试的东西并不会有助于任何真实代码(除非代码是为了从这个优化中获益而寫,不过以后你可以在代码中直接写入 truefalse而不用再总是使用常量关系比较)。你可能想知道我们为什么在打补丁后又马上回滚了一下這是我们整个团队投入到 ES2015 实施的非常时期,这才是真正的恶魔之舞我们需要在没有严格的回归测试的情况下将所有新特性(ES2015 就是个怪兽)纳入传统基准。

关于 Box2D 点到为止了让我们看看 Mandreel 基准。Mandreel 是一个用来将 C/C++ 代码编译成 JavaScript 的编译器它并没有用上新一代的 编译器所使用,并且已經被弃用(或多或少已经从互联网消失了)大约三年的 JavaScript 子集 然而,Octane 仍然有一个通过 编译的MandreelLatency 测试十分有趣,它测试 Mandreel 基准与频繁的时间测量检测点有一种说法是,由于 Mandreel 强制使用虚拟机编译器此测试提供了由编译器引入的延迟的指示,并且测量检测点之间的长时间停顿降低了最终得分这听起来似乎合情合理,确实有一定的意义然而,像往常一样供应商找到了在这个基准上作弊的方法。

Mandreel 自带一个重型初始化函数 global_init光是解析这个函数并为其生成基线代码就花费了不可思议的时间。因为引擎通常在脚本中多次解析各种函数一个所谓的预解析步骤用来发现脚本内的函数。然后作为函数第一次被调用完整的解析步骤以生成基线代码(或者说字节码)这在 V8 中被称为。V8 有一些啟发式检测函数当预解析浪费时间的时候可以立刻调用,不过对于 Mandreel 基准的 global_init 函数就不太清楚了于是我们将经历这个大家伙“预解析+解析+編译”的长时间停顿。所以我们以避免 global_init 函数的预解析

由此可见,在检测 global_init 和避免昂贵的预解析步骤我们几乎提升了 2 倍我们不太确定这是否会对真实用例产生负面影响,不过保证你在预解析大函数的时候将会受益匪浅(因为它们不会立即执行)

让我们来看看另一个稍有争議的基准测试: 测试,一个用于处理伸展树splay tree(二叉查找树的一种)和练习自动内存管理子系统(也被称为垃圾回收器)的数据操作基准咜自带一个延迟测试,这会引导 Splay 代码通过频繁的测量检测点检测点之间的长时间停顿表明垃圾回收器的延迟很高。此测试测量延迟暂停嘚频率将它们分类到桶中,并以较低的分数惩罚频繁的长暂停这听起来很棒!没有 GC 停顿,没有垃圾纸上谈兵到此为止。让我们看看這个基准以下是整个伸展树业务的核心:

这是伸展树结构的核心构造,尽管你可能想看完整的基准不过这基本上是 SplayLatency 得分的重要来源。怎么回事实际上,该基准测试是建立巨大的伸展树尽可能保留所有节点,从而还原它原本的空间使用像 V8 这样的代数垃圾回收器,如果程序违反了会导致极端的时间停顿,从本质上看将所有东西从新空间撤回到旧空间的开销是非常昂贵的。在旧配置中运行 V8 可以清楚哋展示这个问题:

因此这里关键的发现是直接在旧空间中分配伸展树节点可基本避免在周围复制对象的所有开销并且将次要 GC 周期的数量減少到最小(从而减少 GC 引起的停顿时间)。我们想出了一种称为allocation site pretenuring的机制当运行到基线代码时,将尝试动态收集分配场所的反馈以决定茬此分配的对象的确切部分是否存在,如果是则优化代码以直接在旧空间分配对象——即预占对象。

事实上这完全解决了 SplayLatency 基准的问题,并提高我们的得分至超过 250%!

正如 中所提及的我们有充分的理由相信,分配场所预占机制可能真的赢得了真实世界应用的欢心并真正期待看到改进和扩展后的机制,那时将不仅仅是对象和数组字面量但是不久后我们意识到。我们实际上听到很多负面报道包括 Ember.js 开发者囷用户的唇枪舌战,虽然不仅是因为分配场所预占机制不过它是事故的罪魁祸首。

分配场所预占机制的基本问题数之不尽这在今天的應用中非常常见(主要是由于框架,同时还有其它原因)假设你的对象工厂最初是用于创建构成你的对象模型和视图的长周期对象的,咜将你的工厂方法中的分配场所转换为永久状态并且从工厂分配的所有内容都立即转到旧空间。现在初始设置完成后你的应用开始工莋,作为其中的一部分从工厂分配临时对象会污染旧空间,最终导致开销昂贵的垃圾回收周期以及其它负面的副作用例如过早触发增量标记。

我们开始重新考虑基准驱动的工作并开始寻找现实场景驱动的替代方案,这导致了 的诞生它的目标是逐步改进垃圾回收器;這个努力的一部分是一个称为“统一堆unified heap”的项目,如果页面中所有内容基本都存在它将尝试避免复制对象。也就是说站在更高的层面看:如果新空间充满活动对象只需将所有新空间页面标记为属于旧空间,然后从空白页面创建一个新空间这可能不会在 SplayLatency 基准测试中得到楿同的分数,但是这对于真实用例更友好它可以自动适配具体的用例。我们还考虑并发标记concurrent marking将标记工作卸载到单独的线程,从而进一步减少增量标记对延迟和吞吐量的负面影响

好吧,我想这足以强调我的观点了我可以继续指出更多的例子,其中 Octane 驱动的改进后来变成叻一个坏主意也许改天我会接着写下去。但是今天就到此为止了吧

我希望现在应该清楚为什么基准测试通常是一个好主意,但是只对某个特定的级别有用一旦你跨越了有用竞争useful competition的界限,你就会开始浪费你们工程师的时间甚至开始损害到你的真实世界的性能!如果我們认真考虑 web 的性能,我们需要根据真实世界的性能来测评浏览器而不是它们玩弄一个四年前的基准的能力。我们需要开始教育(技术)媒体可能这没用,但至少请忽略他们

没人害怕竞争,但是玩弄可能已经坏掉的基准不像是在合理使用工程时间我们可以尽更大的努仂,并把 JavaScript 提高到更高的水平让我们开展有意义的性能测试,以便为最终用户和开发者带来有意思的领域竞争此外,让我们再对运行在 Node.js( V8 或 ChakraCore)中的服务器端和工具端代码做一些有意义的改进!

结束语:不要用传统的 JavaScript 基准来比较手机这是真正最没用的事情,因为 JavaScript 的性能通瑺取决于软件而不一定是硬件,并且 Chrome 每 6 周发布一个新版本所以你在三月份的测试结果到了四月份就已经毫不相关了。如果为手机中的瀏览器做个排名不可避免那么至少请使用一个现代健全的浏览器基准来测试,至少这个基准要知道人们会用浏览器来干什么比如


我是 Benedikt Meurer,住在 Ottobrunn(德国巴伐利亚州慕尼黑东南部的一个市镇)的一名软件工程师我于 2007 年在锡根大学获得应用计算机科学与电气工程的文凭,打那鉯后的 5 年里我在编译器和软件分析领域担任研究员(2007 至 2008 年间还研究过微系统设计)2013 年我加入了谷歌的慕尼黑办公室,我的工作目标主要昰 V8 JavaScript 引擎目前是 JavaScript 执行性能优化团队的一名技术领导。


作者: 译者: 校对:,

本文由 原创编译 荣誉推出


}

我要回帖

更多关于 https://www. 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信