10 隐藏类与内联缓存:V8 的对象性能学¶
这一章讲什么¶
07 章说过:JavaScript 的对象是动态映射,找属性要「搜索」,而 C++ 只要 「跳到已知偏移」。如果每次访问属性都要真搜一遍,JavaScript 的对象操作 会慢到不可用。这一章讲 V8 的破局法,两件配套的机关:
- 隐藏类(hidden class,也叫 shape 或 map):给对象结构建档, 结构相同的对象共享同一张档案;
- 内联缓存(inline cache, IC):记住「上次在哪找到的」, 下次形状没变就直接跳。
以及代价侧:你的哪些写法会让档案不停重建、让缓存不停作废, 最后把对象整个踢进「字典模式」。这一章直接回应 07 章埋的话: 「不理解这个搜索过程和它的内部结构,就是性能问题的主要来源。」
顶层全景:从「搜索」到「跳偏移」¶
慢速 naive 版:每次读 obj.prop 都在属性集合里线性搜 → 随属性数变慢
│
│ V8 的破局
▼
隐藏类:这个对象有 propA、propB,各占第几个槽位 → 一张「形状档案」
│ 形状相同的 1000 个对象,共享同一张档案
▼
内联缓存:这个函数上次在「形状 S」上找 prop,是在第 2 个槽 → 记住,直接跳
│ 这次对象的形状还是 S?是 → 跳;不是 → 重新找,记新答案
▼
失效太多次 → V8 放弃 → 对象进字典模式(哈希表,灵活但慢)
图说:快与慢的分水岭不是「对象大不大」,而是「形状稳不稳」。
核心原理¶
3.1 隐藏类:对象的形状档案¶
你创建一个对象,V8 就给它建一张隐藏类——一份结构蓝图,写清 这个对象有哪些属性、什么类型、各存在内存的哪个位置1。 可以把它想成 07 章那些描述符的「汇总页」:描述符管单个属性的行为, 隐藏类管整个对象的布局。
关键机制是形状会随属性的增删改而变:对象初生时有一个初始形状, 之后每加一个属性、改一次定义、删一个属性,形状就变一次—— 书里的原话:「创建一个空对象再逐个加十个属性,这个对象就先后有过 十一种形状」2。而且这些形状像 08 章的原型链一样层层叠加, 每次访问属性都要沿形状链查到属性所属的那一层3。 所以结论直接:创建对象时就把属性定全,让形状一次到位4。
主线例子:两种建对象的方式,两种命运。
好版本——属性一次建全、类型一致、顺序固定:
function createOptimizedDrawer(label, contents) {
return { label, contents, isOpen: false, accessCount: 0 };
// ↑ 字符串 ↑ 字符串 ↑ 布尔 ↑ 数字 —— 每次都一样
}
const drawer1 = createOptimizedDrawer('Tax', 'tax documents');
const drawer2 = createOptimizedDrawer('Legal', 'contracts');
const drawer3 = createOptimizedDrawer('HR', 'personnel files');
三个对象共享同一张隐藏类——属性已知、值类型一致、顺序一致, V8 建一份档案反复用5。
坏版本——创建后再按条件逐个加,而且加的顺序还随机:
function createProblematicDrawer(label, contents, config = {}) {
const drawer = { label, contents }; // 形状 1
if (config.secure) {
drawer.lockCode = config.lockCode; // 形状 2(不一定发生)
}
if (config.automated) {
drawer.motorSpeed = config.motorSpeed; // 形状 3、4(不一定发生)
drawer.sensorType = config.sensorType;
}
if (Math.random() > 0.5) {
drawer.priority = 'high'; // 形状 5、6……顺序还分两种
drawer.department = config.department;
} else {
drawer.department = config.department; // 另一种顺序 = 另一种形状!
drawer.priority = 'high';
}
return drawer; // 返回之前,形状最多已经漂移了五次[^6]
}
条件加成让每个对象的长相不确定,顺序随机让「同样的属性集」也分裂成 不同形状。V8 无法预知、无法共享档案,只能见一个建一个。 书里那句经验法则就贴在例子后面:形状不一致,代码就慢7。
3.2 构造函数的隐藏特权¶
「那我在构造函数里逐个 this.x = ... 不也是逐个加属性?」
——技术上是,但 8 章的 class 机制在这里兑现了它的性能价值:
构造函数(以及 class)里逐个赋值也会产生多个过渡形状,
但引擎高度优化这条路径:第一次创建时 V8 就学到了最终形状,
后续每次调用直接一步到位8。而同样的逐个赋值写在「裸的」
const drawer = {}; drawer.x = ... 里,V8 没有函数可以关联这个模式,
换个运行场合就得重新摸索一遍形状9。
所以书里的明确偏好:构造函数和 class,永远优于 obj.prop = value
式的散装构建——不是因为它们省形状转换,而是因为 V8 能学到模式、
优化未来的实例化10。这也是 07 章 defineProperties「一次定义多个更高效」
的真正答案。
3.3 内联缓存(IC):记住上次的位置¶
隐藏类解决「档案从哪来」;内联缓存解决「查档要不要每次重来」。
机制:某处代码执行 obj.prop,V8 找到属性在某个隐藏类的第 N 槽,
就把「形状 → 第 N 槽」记在这段代码旁边(所以叫内联)。
下次执行,先比形状:相同 → 直接按槽位取,整个查找跳过;
不同 → 作废,重新查,记新答案11。
失效是有代价的,而代价会升级:形状老变,IC 里的记录老作废; 作废太多次,V8 的判断很人类——放弃,把这段代码访问的对象 踢进慢速通道,从此什么优化都不再给12。这个过程叫去优化 (deoptimization),它对开发者完全隐形,唯一的症状是「代码变 慢了」13。
隐形到什么程度?书里自己承认:隐藏类问题极难诊断, 好在 Chrome devtools 这类工具能暴露应用生成了多少隐藏类14—— 「我们后面的章节会讲这些工具」,而后面的章节,MEAP 还没有。
3.4 快速属性 vs 字典模式:最后一次降级¶
V8 的属性存法分两档15:
| 模式 | 存法 | 访问 |
|---|---|---|
| 快速属性 | 线性内存布局,隐藏类直接给偏移 | 常数时间,按偏移取 |
| 字典(慢)属性 | 哈希(按内容算出位置的)表 | 灵活(随便增删改),但每次要重新算位置 |
对象什么时候被降级?「太不可预测」的时候:动态加的属性太多、 属性被删、形状五花八门——V8 换哈希表,用性能换灵活16。 第 5 章的总结把收益说得很直白:形状一致时,内联缓存加持的属性访问 比字典模式快 100 倍17。
3.5 稳定形状清单(书里的五条)¶
要保持快道,原书给了五条可执行的建议18:
- 属性在构造函数里一次初始化全,别事后补;
- 形状保持一致:同样的属性、同样的顺序——顺序不同就是不同形状,
{ label, isLocked }和{ isLocked, label }在 V8 眼里是两种对象19; - 别
delete属性——可能逼出字典模式; - 数组用数字下标,别和命名属性混用(09 章的存储机制在这里收尾);
- 属性的值别换类型(字符串改数字这类)。
3.6 这不是教科书练习:Node.js 自身的整改¶
作者把这套优化用到了自己工作的地方,作为本章的收束,也是全书 「实现知识→生产收益」路径的最佳案例:十年前他刚开始给 Node.js 贡献代码时,Node 内部也是一处一处给对象加属性;后来全面改成 「属性齐了才建对象、顺序固定、类型一致」,全世界所有跑在 Node.js 上的应用都因此变快了20。引擎优化不是象牙塔知识, 是运营一个运行时平台时的日常决策。
作者的判断与证据¶
有证据的判断:
- 「十一种形状」「最多五次形状漂移」是机制推演,基于隐藏类的公开实现原理, 书中自己注明「有意做了简化」并指向 V8 官方文档21。
- 「快 100 倍」出自第 5 章总结,方向与 V8 官方对 IC 的叙述一致22。
作者的主张:
- 「构造函数/ class 永远优于散装构建」是性能导向的实践立场; 反例(一次性脚本、形状本来就多样的场景)书中未讨论。
- 五条清单是经验法则,不是规范;不同引擎的阈值和行为不同。
判断(我们的,不是书里的): 10 章是全书的「点题章」:ConsString(03)、 SMI(05)、隐藏类(10)是同一个思想的三次应用——引擎给「最常见、最有规律」的 用法修专用快道,规律破坏时静默降级。记住这个结构,比记住五诫中的任何一条 都更抗版本过时。 如果错,会错在: 如果未来的运行时对「无规律代码」也能保持接近优化的速度 (比如靠更强的推断或全新的对象布局),「守形状」会从性能必需降级为风格偏好—— 但「降级无提示、只能靠工具观察」的现象本身不会消失,只是代价变小。
边界与局限¶
- 全书对 V8 优化的讲解自我声明为简化版,作者建议想深入的人去读 V8 官方文档(v8.dev/docs/hidden-classes)23。真实实现还有 属性线程内缓存、反馈向量(V8 记录「这段代码见过什么形状」的内部表)等本书未涉及的层。
- 所有机制都是实现层,不是规范:其他引擎有类似机制但细节不同, 且 V8 版本间会变(03 章那个「优化会过时」的警告在此同样适用)。
Object.setPrototypeOf会迫使形状重建(08 章留的尾巴): 动态换原型的性能代价,原书没有明说,但机制上可以推出—— 形状包含原型,换原型 = 换形状。
可带走的¶
- 隐藏类 = 对象的结构档案;属性一次建全,档案一次到位。
- 空对象逐个加十个属性 = 十一种形状;条件加属性 + 随机顺序 = 形状灾难。
- 构造函数/class 的价值之一:V8 能学到最终形状,后续实例化一步到位。
- 内联缓存记「形状→槽位」;形状变,缓存废;废太多,去优化。
- 属性顺序不同 = 形状不同;delete 可能触发字典模式。
- 快/字典两档存法;优化到位时属性访问比字典快 100 倍。
- 诊断形状问题:Chrome devtools;症状只有一个——慢。
- Node.js 自己这么改过:生产系统的全局提速就是这么来的。
原文地图¶
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| 隐藏类的定义 | 5.8 Hidden classes and shapes: the secret object blueprint | text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:7(搜「shapes or maps」) |
| 形状随增删改而变 | 同上 | text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:19(搜「shape changes」) |
| 「十一种形状」与一次建全 | 同上 | text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:25(搜「11 different shapes」) · :38(搜「all at once」) |
| 构造函数的优化特权 | 同上 | text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:45(搜「What about constructors」) · :94(搜「learn the pattern」) |
| 坏例子:五次形状漂移 | 同上 | text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:138(搜「bad example」) · :186(搜「five times」) |
| 「形状不一致 = 慢」 | 同上 | text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:144(搜「inconsistent shapes」) |
| 隐形难诊断 | 同上 | text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:199(搜「difficult to diagnose」) |
| 内联缓存与放弃治疗 | 5.9 Inline caches: V8's speed boost system | text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:7(搜「give up」) · :7(搜「deoptimization」) |
| 快速/字典两档 | 同上 | text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:40(搜「Fast properties」) · :48(搜「hash table」) |
| 五条实践建议 | 同上 | text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:66(搜「Initialize all properties」) |
| 顺序不同 = 形状不同 | 同上 | text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:83(搜「different orders」) |
| Node.js 自身整改 | 同上 | text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:115(搜「A decade ago」) |
| 「快 100 倍」总结 | 5.10 Summary | text/25-ch05-10-5-10-summary.txt:40(搜「100x faster」) |
-
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 7 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:7,搜「structural blueprints」)。「隐藏类里存着属性描述符」见第 13 段(搜「where those are held」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 19 段与第 25 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:25,搜「11 different shapes」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 25 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:25,搜「very similar to the way the prototype chain works」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 38 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:38,搜「defined all at once」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 102–128 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:102,搜「same shape」)。「共享同一份内部表示」见第 118 段(搜「reuses it」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 151–186 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:151,搜「createProblematicDrawer」;:186搜「up to five times」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 144 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:144,搜「inconsistent shapes means slower」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 51–67 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:67,搜「each individual assignment」;:67搜「knows the final shape」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 73–88 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:88,搜「no constructor to associate」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 94 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:94,搜「always preferable」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 7 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:7,搜「remember where properties are located」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 7 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:7,搜「give up and force your objects into a slow mode」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 7 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:7,搜「completely invisible」)。 ↩ -
出处:「5.8 Hidden classes and shapes: the secret object blueprint」第 199 段(
text/23-ch05-08-5-8-hidden-classes-and-shapes-the-secret-object-.txt:199,搜「difficult to diagnose」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 34–43 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:40,搜「Fast properties」;:43搜「Slow properties」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 48 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:48,搜「too unpredictable」)。 ↩ -
出处:「5.10 Summary」第 40 段(
text/25-ch05-10-5-10-summary.txt:40,搜「100x faster」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 60–78 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:66,搜「Initialize all properties in constructors」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 83–103 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:83,搜「different orders」;:90搜「drawer1 = {」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 115 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:115,搜「A decade ago」与「greatly boost the performance」)。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 20 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:20,搜「intentionally oversimplifying」)与 v8.dev 文档的指引(同段)。 ↩ -
出处:「5.10 Summary」第 40 段(
text/25-ch05-10-5-10-summary.txt:40,搜「100x faster」)。补充(不在书里):V8 官方博客对内联缓存的解释见 v8.dev/docs/hidden-classes,与本书机制叙述一致。 ↩ -
出处:「5.9 Inline caches: V8's speed boost system」第 20 段(
text/24-ch05-09-5-9-inline-caches-v8-s-speed-boost-system.txt:20,搜「v8.dev/docs/hidden-classes」)。 ↩