Ede's Blog

V8 Blog的阅读笔记

工作以后,接触英文资料较少。平时阅读英文文档,为了效(yan)率(shi),google翻译已三年。三年内,除了对前端知识稍以熟练,其余收获不多。故立小目标,抽空阅读V8的博客尽可能先读原文,后看翻译

在我的实践理解中,对前端而言,阅读V8博客,哪怕占用了我下半年大量的空余时间,也是收获甚多。建议细读。(人懒碎事多,导致空闲时间并不多,截止2019.12.31,阅读进度100/100)

以下仅为各篇文章摘要,具体内容请自行阅读。

V8技术简介

  1. Digging into the TurboFan JIT:仅提及V8采用代号了为TurboFan的多层级架构的编译器。

  2. Code caching:V8采用了代码缓存的技术,用节省切换页面时字节码转化为机器码所需要的时间,ScriptCompiler::CompileOptions(未展开简述)

  3. 【REC】Getting garbage collection for free:V8会在浏览器相对空闲时采取GC,其拆分为多个具备执行上限的不同优先级任务。和大多数GC一样,V8也会区分新生代和次世代。新生代采用了semi-space的策略(分为两个半空间,激活一个,用以存放对象,此半空间满时,执行清理,已标记的活跃的对象将移动次世代空间,未被标记的移动到另一个半空间,不活跃的则会被回收,两个半空间空闲状态转化)。次世代则采用mark-and-sweep(标记-扫描)策略,次世代的回收耗时较长,为了避免等待,该标记收集会被拆分成多个执行时间小于5ms的子任务。(该方法也是常用的方法,缺点是执行期间会挂起正常程序,以及多次执行会产生碎片,V8如何克服缺点并没有展开简述)

  4. Custom startup snapshots:V8采用快照的方式,减少页面初始化时全局对象(正则等)初始化的耗时。每打开一个页面,将创建一份具备独立上下文的快照副本。

  5. Jank Busters Part One:交代了导致V8Jank的两个重要原因:GC跟踪过多的ArrayBufferV8向Chrome提供过多句柄(允许操作V8数据)。这两个情况常发生在WebGL的渲染上。对这两种情况,V8做了以下优化:对ArrayBuffer剥离原有的GC规则,作针对性检查( per-access checks在原理上增大耗时,但remove redundant checks却实际上减少了jank,V8未进一步交代具体实现,本人想象力缺乏也没有脑补出来)。以及针对这些数据多数只读不写的特性,通过实现逃逸分析进行优化。(锁消除 & 标量替换)

  6. There’s Math.random(), and then there’s Math.random():介绍了Math.random从MWC1616xorshift128+的过程。

  7. V8 extras:V8提供extras模式 API,使可以用javascript提供内置通用的全局对象(称为self-hosted),eg:Promise对象。

  8. RegExp lookbehind assertions:V8支持了正则的后置断言

  9. Experimental support for WebAssembly in V8:V8支持WebAssembly努力学Rust中

  10. Jank Busters Part Two: Orinoco:介绍了名为Orinoco的GC优化手段。将对象从新生代转移到次世代的过程中,指针更换的消耗是昂贵的。GC的优化策略:1. 因为指针更换和次世代的压缩没有依赖关系,所以可以并行执行。2. 采用新的remembered set,简化了原RS相互引用的情况,使追踪指针的命令能够并行(没看懂,日后再算) 3. 采用了黑色标记法(因为次世代跟持久,所以可以提前标记其活跃)。

  11. ES2015, ES2016, and beyond:介绍了V8如何健壮地支持ES Next

  12. V8 at the BlinkOn 6 conference:此博客是一些列视频,先Mark

  13. Firing up the Ignition interpreter:介绍了V8为移动端定制了新的解析器Ignition,意在减少内存占用。这里用到了底层一点的技术,不是很了解。

  14. Optimizing V8 memory consumption:介绍了v8垃圾收集的优化,让内存指标可视化、针对不同的场景执行不同的回收策略(比如对一些内存敏感的设备执行更积极的回收)、及早释放解析器、通过合并数据使数据存储更紧凑等。

  15. WebAssembly browser preview:浏览器支持WebAssembly

  16. V8 love Node.js:可以在Chromium中调试node、支持async/await

  17. How V8 measures real-world performance拓展:经过比较发现,Speedometer更接近真实的数据。

  18. Speeding up V8 regular expressions:用C++实现正则,为避免变慢,请勿更改Reg的原型

  19. One small step for Chrome, one giant heap for V8:Chrome DevTools增加一个功能,在内存快耗尽时断点

  20. Help us test the future of V8!:求助贴,通过开启实验性功能来测试新的解析器和编译器

  21. High-performance ES2015 and beyond:鼓励开发人员使用ES2015+,仅向不支持的浏览器提供编译版本,这通常能提高页面执行速度。(然而自家产品也没有做到,哈哈哈)

  22. Fast for-in in V8:优化了for in:对象是否含有enumCache(缓存),对于没有缓存的情况,引入KeyAccumulator类,来处理一些简单的键值的枚举,此举提升了众多网站4%的执行速度。

  23. Retiring OctaneOctane工具在现阶段已不利于测评具体世界的js运行情况,谋求一个不依赖于浏览器、分类、免费、关键点在于时间的基准测试工具,如speedometer

  24. Launching Ignition and TurboFan:已发布新的点火(Ignition)和涡轮(TurboFan)

  25. About that hash flooding vulnerability in Node.js:预编译版本的Node,启用快照(加快启动速度)时,因为随机种子固定,可提交有规律的HTTP头进行Hash攻击,从而导致Node服务器拒绝服务。新的解决方法是,在快照反序列化后,重新生成新的随机种子。

  26. 【REC】Fast properties in V8:解析V8如何处理对象属性。

    • 数组单独存储:因为可以动态删除,会造成存储上有孔(索引没有值)的现象(有标记位标记不存在的属性,索引没有值会触发原型读取)、稀疏的数组将会以字典形式存储

    • 属性和值可以是数组或字典的形式:因不利于优化,简单对象并不是以字典方式存储,复制对象可能会一字典存储,但将变成慢属性(运行有效的属性删除,但访问速度较慢)

    • HiddenClass用于描述对象形状:第一个字段即指向形状,第三个字段存储指针位置等,但没有索引计数,添加属性即更改HiddenClass

  27. CodeStubAssembler builtins:介绍CodeStubAssembler,一种定制的,与平台无关的汇编器。可提供接近汇编的性能。

  28. Elements kinds in V8:介绍了V8中不同的数据类型(虽然JS本身不作区分)。这些类型可以在运行中被改变,但只能从特定类型到更一般类型。大类有PACKEDHOLEY(有孔,代表稀疏)。同时针对类型,给出了一些性能优化建议:避免读取超过数组长度的索引,如:不要再循环条件中复制(可以在循环内部)、避免元素种类转换、优先数组而不是类数组、避免多态,包括参数多态(这个建议牺牲了灵活性,拓展)、最好在初始化时声明所有值,或者初始化空值,而后采用push方法(不要new Array,然后根据索引复制)。

  29. Temporarily disabling escape analysis:暂时停止转义分析(eg:通过语法转换避免避免创建对象)

  30. An internship on laziness: lazy unlinking of deoptimized functions:剔除一些潜在的无效代码

  31. Announcing the Web Tooling Benchmark:V8发布了新的基准测试工具,也做出了一些回馈社区的举动

  32. Taming architecture complexity in V8 — the CodeStubAssembler:CSA(CodeStubAssembler)入门介绍

  33. Optimizing ES2015 proxies in V8:介绍了将Proxy的运行从C++端转移到CSA上,在JS运行时执行从而将语言跳转的次数从4降为0。同时也优化了注入gethasset

  34. Orinoco: young generation garbage collection:介绍GC中的对新生代的算法、半空间算法并行标记法(MS)、并行消除(ME)。参考【3】

  35. JavaScript code coverage:介绍了Chrome的source代码覆盖工具,并分为尽力而为(遍历整个对的活动函数,但可能因GC导致数据丢失)、和精准覆盖(提供计数,且不被GC)

  36. Chrome welcomes Speedometer 2.0!:Speedometer 2.0版本,并基于此加速了React一倍解析执行的速度

  37. Optimizing hash tables: hiding the hash code:字典存储数据时【参考26】,会生成Hash(会被存储,可能存储在一个对象的属性数组上以节省内存)

  38. Lazy deserialization:为了极速启动页签,V8创建快照【参考4】,但也因序列化原因,导致内存消耗变大。因为V8延迟了反序列化时机,仅在用到时反序列化。

  39. Tracing from JS to the DOM and back again:使内存泄露调试更容易(eg:添加事件监听但不销毁会导致内存泄露,Menory中的Leak可观察挂载在window对象或事件)

  40. Background compilation:新架构将对JS的编译剥离了主线程(Parse、Compile在后台线程Stream Source,execute在主线程)

  41. Improved code caching:V8缓存并会反序列化编译后的代码

  42. Adding BigInts to V8:考虑提供对大数bigInt的支持

  43. Concurrent marking in V8:改进三色标记的增量标记【参考3】,支持并发执行

  44. Embedded builtins:嵌入式函数库(基于CSA)占用了更多的空间(即使开启了反序列化惰性加载)【参考38】。考虑内嵌二进制(由于访问隔离对象和过程消耗大,仅实现内嵌不一定是优化的,文中还给出了一些优化,如全局内置不常用对象等)

  45. Liftoff: a new baseline compiler for WebAssembly in V8:新的WASM编辑器(前半部分没看懂,后半部分介绍了wasm结合TurboFan的好处,如静态类型可立即生成优化代码、代码可预测而不需要预热,仅liftoff的话,执行的速度可能会较TurboFan慢)

  46. Celebrating 10 years of V8:V8十年历史,虽然在两次大事件中稍微降低(支持ES5、Spectre漏洞),但总的性能还是提升了4倍

  47. Improving DataView performance in V8:改进DataView(一种和TypedArray并列的底层访问数据的结构)。并提到了Torque,一种降低编写CSA难度的语言。

  48. Getting things sorted in V8:展开描述Array.sort,推荐避免一些写法以实现更好的性能。原排序使用了quickSort(一般Log(n),最坏O(n2)),但后来转变为了TimSort(一般O(n),最坏nlog(n));虽然效率略有降低,但保证每次返回的结果一致。比如sort(sort(A)) === sort(A)、或者[1,2,3,4,5,6,7,8,9,10,11].sort(function(){return 0;}) \> [6, 1, 3, 4, 5, 2, 7, 8, 9, 10, 11](旧版本,新版本会返回原数组)

  49. Faster async functions and promises:setTimeout\setImmediate(task),async/promise(microtask)、一般执行顺序Promise > Mutate > setTimeoutawait的关键词比较接近Promise.resolve().then()(旧的标准,要求Promise在大部分时间都throwable),新标准只需要一个Promise即可。现阶段,await/async在chrome上已优于手写Promise代码。

  50. Speeding up spread elements:优化了[...array]的性能。假设值是array,会比其他对象能更快地析构(因为不需要创建复杂的Iterable)

  51. Trash talk: the Orinoco garbage collector:又一次介绍了现阶段的V8GC。GC三阶段:标记、清除、压缩。而V8的GCOrinoco则包含一些特性:并行,增量,并发。

  52. JIT-less V8:某些平台禁止非特权应用程序对可执行内存进行访问,导致V8无法分配修改可执行内存。V8添加无JIT模式的运行。

  53. Blazingly fast parsing, part 1: optimizing the scanner:介绍了V8快速解析JS代码的优化内容、空格扫描、标识符扫描(Perfect hash function)等。也对开发者给出了优化建议:减少源码、去除不必要空格、尽可能避免使用非ASCII标识符等。

  54. Code caching for JavaScript developers:介绍了V8的代码缓存(字节码级别,【参考41】),并提出了优化建议:若无必要,不要更新代码、请勿更改网址、少用A/B测试、提取公共代码库、分解大文件(eg:1MB)&合并小文件(eg:1Kb)、避免内敛脚本(无法利用缓存)、使用PWA缓存等的。(感觉大多数建议并不实用,因为业务在大多数优先级优于代码性能)

  55. Blazingly fast parsing, part 2: lazy parsing:介绍了V8惰性解析的应用,从而加快启动速度,减少内存开销。

  56. A year with Spectre: a V8 perspective:介绍了spectre漏洞发布后,V8一年的举动。

  57. Faster and more feature-rich internationalization APIs:优复了国际性API的错误 以及优化 性能。

  58. Code caching for WebAssembly developers:缓存WASM(128kb~150MB)

  59. The cost of JavaScript in 2019:介绍了2019V8版本的性能,得益于新的引擎,已大大减少了解析和编译的成本,目前前端瓶颈已经几种在网络下载上。并给出了一些建议:缩短下载时间,避免执行时间过长,避免大型内联脚本(建议1KB以内)。

  60. Emscripten and the LLVM WebAssembly backend:后端将默认使用LLVM而不是fastcomp。(性能更优,大小更小)。emscripten(编译asm.js的工具),将支持编译webassembly,以达到更好的性能。

  61. The story of a V8 performance cliff in React:V8切换、弃用形状【参考28】,在React上带来了性能瓶颈。(V8推荐,不要再初始化时将数值字段设置为null,因为这样会放弃字段跟踪)

  62. A lighter V8:V8的精简模式(指初始阶段,最终还是会回落到正常的版本),介绍了延迟反馈的实现

  63. Improving V8 regular expressions:正则表达的分层策略(对高频使用的正则编译为本地机器码)

  64. Outside the web: standalone WebAssembly binaries using:尝试脱离JS运行wasm

版本发布

因博客中技术简介和发布信息混在一起,发布信息提取于此,顺带了解V8对JS语法的支持进度,以及ES5+语法。

值得一提,2019年,V8对WebAssembly投入了大量的资源,可以预测在不久的将来,一些对性能有一定要求的程序,也会在浏览器上运行起来。