惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
F
Fortinet All Blogs
博客园 - 叶小钗
阮一峰的网络日志
阮一峰的网络日志
月光博客
月光博客
爱范儿
爱范儿
博客园_首页
S
Security @ Cisco Blogs
H
Heimdal Security Blog
Cyberwarzone
Cyberwarzone
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tailwind CSS Blog
T
Threatpost
博客园 - 司徒正美
The GitHub Blog
The GitHub Blog
WordPress大学
WordPress大学
S
Security Affairs
AWS News Blog
AWS News Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cisco Talos Blog
Cisco Talos Blog
博客园 - 聂微东
P
Privacy & Cybersecurity Law Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
NISL@THU
NISL@THU
G
GRAHAM CLULEY
人人都是产品经理
人人都是产品经理
L
LangChain Blog
The Register - Security
The Register - Security
L
LINUX DO - 最新话题
博客园 - 【当耐特】
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
Application and Cybersecurity Blog
Application and Cybersecurity Blog
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
P
Proofpoint News Feed
IT之家
IT之家
云风的 BLOG
云风的 BLOG
量子位
D
Darknet – Hacking Tools, Hacker News & Cyber Security
PCI Perspectives
PCI Perspectives
腾讯CDC
大猫的无限游戏
大猫的无限游戏
美团技术团队
N
News | PayPal Newsroom
Recent Commits to openclaw:main
Recent Commits to openclaw:main
D
Docker
A
Arctic Wolf
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Cloudbric
Cloudbric

囧克斯

奔四了,写给40岁的自己 有序退网 我和 Vue.js 的十年 我参与《代码之外 Beyond Code》的故事 博客站迁移至 VitePress 的备忘 写给我的奶奶 中文格式化小工具 zhlint 及其开发心得 vue-mark-display:用 markdown 语法轻松撰写幻灯片 我对技术会议的一些看法 VueConf Hangzhou 见闻 第四届 CSSConf CN 见闻 [译]Web 表单的未来 [译]苹果正在做一些他们的程序员明摆着不想要的东西 [译]为什么我不会无偿加班且你也不应该 [译]如果管理是唯一可走的路,那就完蛋了 [译]如何撰写 Git 提交信息 [译]C 程序的原则 Vue 2.0 来了! Weex 近 4 个月的开源之路 Weex 在 JS Runtime 内的多实例管理 我理解的 SPA 我理解的 Flux 架构 【整理】Vue 2.0 自 beta 1 到 beta 4 以来的主要更新 通过一张图走进 Vue 2.0 Code Review for Vue 2.0 Preview Vue 2.0 发布啦! 务实的小而美 Vue.js 1.0.0 发布了! [译]如何成为一名卓越的前端工程师 手机淘宝前端的图片相关工作流程梳理 [译]如何让办公室政治最小化 Vue.js 源码学习笔记 从原型到发布——“团队时间线” 1.0 开发心得 Vue + webpack 项目实践 用 Koa 写服务体验 webcomponents 笔记 之 配置管理 小秀个人的13~14年摄影作品 (共19张) 由今年D2前端论坛想到的 [译]CSS命名神马的真心难 [译]Git 分支的最佳实践 [译]撰写可测试的 JavaScript [译]语义化版本管理 [译]通过HTML5 Canvas API调节图像的亮度和颜色 [译]视觉差,走起! [译]Chrome开发者工具中评估性能的五大新特性 精气神儿 细节无微不至,彩屏让人又爱又恨——新老“神机”大对决:Nokia 1050 vs Nokia 1202 秦升拿到红牌之后…… 用Sass重新整理自己的博客主题样式 Connect中间件使用手册 巧用 RequireJS Optimizer 给传统的前端项目打包 编辑器小调查结果 [译]JSLint 文档 烟火——写给蛇年的傲游和我 小秀个人的全年摄影作品 (共15张) 2012年终毫无正能量的总结 标签?ID?还是CLASS? 微创新=伪创新 HTML5峰会归来 分享bookmarklet一则:随意阅读 国际羽联和中国队之间的恶性循环 ZeroClipboard 学习笔记 “思考人生” 听杨东杰弹吉他 学习精髓 网站装修笔记20120426 网站装修笔记20120414 网站装修笔记20120406 分享Typecho插件:百度统计助手 我的得奖感言 网站装修笔记20120331 用CSS3制作尖角标签按钮样式 分享Typecho插件:Markdown 解析器 + 编辑器 分享Typecho皮肤:我的字很大 汇总自己过去的一些HTML5科普文章 html5中的消息通信简介 + 我的新网站开张 网站装修计划 Typed Arrays 是神马? 文明看球 HTML5中的文件处理 之 File Writer API HTML5中的文件处理 之 File API 独生子女、互相等和不耐烦 IndexedDB技术简介(四) IndexedDB技术简介(三) IndexedDB技术简介(二) IndexedDB技术简介(一) 把博客的字体进一步调大,同时去掉了侧边栏 “模仿别人是为了找到自己” 写给我未婚妻的2011年 写给自己的2011年 手把手教你入门EaselJS做HTML5动画 “吃了吧,不吃就浪费了” 写给傲游的2011年 所谓专业 写给HTML5的2011年 对HTML5中LocalStorage的一些使用建议 回味一下我2011年看到的最过瘾的5场足球比赛 浅浅浅谈开饭店被顾客吃出“异物”的用户体验 前端的本职 继续对Mac穷追猛打 之 修改原生按钮文字大小的技巧
[译]JavaScript V8性能小贴士
勾三股四 · 2013-09-29 · via 囧克斯

译自:Performance Tips for JavaScript in V8

简介 ​

关于如何巧妙提高V8 JavaScript性能的话题,Daniel Clifford在Google I/O上做了一次非常精彩的分享。Daniel鼓励我们“追求更快”,认真的分析C++和JavaScript之间的性能差距,根据JavaScript的工作原理撰写代码。在Daniel的分享中,有一个核心要点的归纳,我们也会根据性能指导的变化保持对这篇文章的更新。

最重要的建议 ​

最重要的是要把任何性能建议放在特定的情境当中。性能建议是附加的东西,有时一开始就特别注意深层的建议反而会对我们造成干扰。你需要从一个综合的角度看待你的Web应用的性能——在关注这些性能建议之前,你应该找PageSpeed之类的工具大概分析一下你的代码,也算是跑个分先。这会防止你过度优化。

对Web应用的性能优化,几个原则性的建议是:

  • 首先,未雨绸缪
  • 然后,找到症结
  • 最后,修复它

为了完成这几个步骤,理解V8如何优化JS是一件很重要的事情,这样你就可以根据其对JS运行时的设计撰写代码。同样重要的是掌握一些帮得上忙的工具。Daniel也交代了一些开发者工具的用法,它们刚好抓住了一些V8引擎设计上最重要的部分。

OK。开始V8小贴士。


隐藏类 ​

JavaScript限制编译时的类型信息:类型可以在运行时被改变,可想而知这导致JS类型在编译时代价昂贵。那么你一定会问:JavaScript的性能有机会和C++相提并论吗?尽管如此,V8在运行时隐藏了内部创建对象的类型,隐藏类相同的对象可以使用相同的生成码以达到优化的目的。

比如:

function Point(x, y) {
  this.x = x;
  this.y = y;
}

var p1 = new Point(11, 22);
var p2 = new Point(33, 44);
// At this point, p1 and p2 have a shared hidden class
// 这里的p1和p2拥有共享的隐藏类
p2.z = 55;
// warning! p1 and p2 now have different hidden classes!
// 注意!这时p1和p2的隐藏类已经不同了!

在我们为p2添加“z”这个成员之前,p1和p2一直共享相同的内部隐藏类——所以V8可以生成一段单独版本的优化汇编码,这段代码可以同时封装p1和p2的JavaScript代码。我们越避免隐藏类的派生,就会获得越高的性能。

结论 ​

  • 在构造函数里初始化所有对象的成员(所以这些实例之后不会改变其隐藏类)
  • 总是以相同的次序初始化对象成员

数字 ​

当类型可以改变时,V8使用标记来高效的标识其值。V8通过其值来推断你会以什么类型的数字来对待它。因为这些类型可以动态改变,所以一旦V8完成了推断,就会通过标记高效完成值的标识。不过有的时候改变类型标记还是比较消耗性能的,我们最好保持数字的类型始终不变。通常标识为有符号的31位整数是最优的。

比如:

var i = 42; // 这是一个31位有符号整数
var j = 4.2; // 这是一个双精度浮点数

结论 ​

  • 尽量使用可以用31位有符号整数表示的数。

数组 ​

为了掌控大而稀疏的数组,V8内部有两种数组存储方式:

  • 快速元素:对于紧凑型关键字集合,进行线性存储
  • 字典元素:对于其它情况,使用哈希表

最好别导致数组存储方式在两者之间切换。

结论 ​

  • 使用从0开始连续的数组关键字
  • 别预分配大数组(比如大于64K个元素)到其最大尺寸,令其尺寸顺其自然发展就好
  • 别删除数组里的元素,尤其是数字数组
  • 别加载未初始化或已删除的元素:
a = new Array();
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // 杯具!
}
//vs.
a = new Array();
a[0] = 0;
for (var b = 0; b < 10; b++) {
  a[0] |= b;  // 比上面快2倍
}

同样的,双精度数组会更快——数组的隐藏类会根据元素类型而定,而只包含双精度的数组会被拆箱(unbox),这导致隐藏类的变化。对数组不经意的封装就可能因为装箱/拆箱(boxing/unboxing)而导致额外的开销。比如:

var a = new Array();
a[0] = 77; // 分配
a[1] = 88;
a[2] = 0.5; // 分配,转换
a[3] = true; // 分配,转换

下面的写法效率更高:

var a = [77, 88, 0.5, true];

因为第一个例子是一个一个分配赋值的,并且对a[2]的赋值导致数组被拆箱为了双精度。但是对a[3]的赋值又将数组重新装箱回了任意值(数字或对象)。第二种写法时,编译器一次性知道了所有元素的字面上的类型,隐藏隐藏类可以直接确定。

结论 ​

  • 初始化小额定长数组时,用字面量进行初始化
  • 小数组(小于64k)在使用之前先预分配正确的尺寸
  • 请勿在数字数组中存放非数字的值(对象)
  • 如果通过非字面量进行初始化小数组时,切勿触发类型的重新转换

JavaScript编译 ​

尽管JavaScript是个非常动态的语言,且原本的实现是解释性的,但现代的JavaScript运行时引擎都会进行编译。V8(Chrome的JavaScript)有两个不同的运行时(JIT)编译器:

  • “完全”编译器,可以为任何JavaScript生成优秀的代码
  • 优化编译器,可以为大部分JavaScript生成伟大(汗一下自己的翻译)的代码,但会更耗时

完全编译器 ​

在V8中,完全编译器会以最快的速度运行在任何代码上,快速生成优秀但不伟大的代码。该编译器在编译时几乎不做任何有关类型的假设——它预测类型在运行时会发生改变。完全编译器的生成码通过内联缓存(ICs)在程序运行时提炼类型相关的知识,以便将来改进和优化。

内联缓存的目的是,通过缓存依赖类型的代码进行操作,更有效率的掌控类型。当代吗运行时,它会先验证对类型的假设,然后使用内联缓存快速执行操作。这也意味着可以接受多种类型的操作会变得效率低下。

结论 ​

  • 单态操作优于多态操作

如果一个操作的输入总是相同类型的,则其为单态操作。否则,操作调用时的某个参数可以跨越不同的类型,那就是多态操作。比如add()的第二个调用就触发了多态操作:

function add(x, y) {
  return x + y;
}

add(1, 2);     // add中的+操作是单态操作
add("a", "b"); // add中的+操作变成了多态操作

优化编译器 ​

V8有一个和完全编译器并行的优化编译器,它会重编那些最“热门”(即被调用多次)的函数。优化编译器通过类型反馈来使得编译过的代码更快——事实上它就是使用了我们之前谈到的ICs的类型信息!

在优化编译器里,操作都是内联的(直接出现在被调用的地方)。它加速了执行(拿内存空间换来的),同时也进行了各种优化。单态操作的函数和构造函数可以整个内联起来(这是V8中单态操作的有一个好处)。

你可以使用单独的“d8”版本的V8引擎来获取优化记录:

d8 --trace-opt primes.js

(其会把被优化的函数名输出出来)

不是所有的函数都可以被优化,有些特性会阻止优化编译器运行一个已知函数(bail-out)。目前优化编译器会排除有try/catch的代码块的函数。

结论 ​

  • 如果存在try/catch代码快,则将性能敏感的代码放到一个嵌套的函数中:
function perf_sentitive() {
 // 把性能敏感的工作放置于此
}

try {
 perf_sentitive()
} catch (e) {
 // 在此处理异常
}

这个建议可能会在未来发生改变,因为我们会在优化编译器里开启try/catch代码块。你可以通过使用上述的d8选项“--trace-opt”得到更多有关这些函数的信息来检验优化编译器如何排除这些函数。

d8 --trace-opt primes.js

取消优化 ​

最终,编译器的性能优化是有针对性的——有时它的变现并不好,我们就不得不回退。“取消优化”的过程实际上就是把优化过的代码扔掉,恢复执行完全编译器的代码。重优化可能稍后再打开,但是短期内性能会下降。尤其是取消优化的发生会导致其函数的变量的隐藏类的变化。

结论 ​

  • 回避在优化过后函数内隐藏类改变

你可以像其它优化一样,通过V8的一个日志标识来取消优化。

d8 --trace-deopt primes.js

其它V8工具 ​

顺便提一下,你还可以在Chrome启动时传递V8跟踪选项:

"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --js-flags="--trace-opt --trace-deopt"

额外使用开发者工具分析,你可以使用d8进行分析:

% out/ia32.release/d8 primes.js --prof

它通过内建的采样分析器,对每毫秒进行采样,并写入v8.log。

回到摘要…… ​

重要的是认识和理解V8引擎如何处理你的代码,进而为优化JavaScript做好准备。再次强调我们的基础建议:

  • 首先,未雨绸缪
  • 然后,找到症结
  • 最后,修复它

这意味着你应该通过PageSpeed之类的工具先确定你的JavaScript中的问题,在收集指标之前尽可能减少至纯粹的JavaScript(没有DOM),然后通过指标来定位瓶颈所在,评估重要程度。希望Daniel的分享会帮助你更好的理解V8如何运行JavaScript——但是也要确保专注于优化你自身的算法!

参考资料 ​