先划重点:使用奇技淫巧,要知其然并知其所以然,这样就算 V8 更新也可以找出解决方案。
在第一章 《奇技淫巧学 V8 之一,对象访问模式优化》 中我们提出将对象由字典模式优化成为快速模式有如下两种方案:
- 调用 Runtime Call (%ToFastProperties) 。
- 设置成为一个函数(或对象)的原型。
但是为什么通过上述两(奇)种(技)操(淫)作(巧)可以优化对象为快速模式了?
我们要先从 Runtime Call (%ToFastProperties) 说起,在 V8 内部其实是直接调用了 v8::internal::JSObject::MigrateSlowToFast 方法,但这方法的 namespace 为 v8::internal 无法直接在外部(包括 JavaScript 与 外部 C++ 代码)进行调用。
虽然我们不能直接进行调用,但是我们可以通过一些方法的 side effects 来隐式调用,而将对象设置成为一个函数(或对象)的原型就是运用了这个技术。
在 V8 中有如上图的途径可以隐式调用到 MigrateSlowToFast 方法,而我们的奇技淫巧也正是利用了这条通路。
当 StoreIC(LoadIC)处于非初态时,每次 IC 状态的变更 V8 均会调用 MakePrototypesFast,最终会调用到 MigrateSlowToFast 将处于字典模式的对象优化成为快速模式。
IC 相关知识详见第三章《奇技淫巧学 V8 之三,多态内联缓存 PICs》
我们来具体分析下之前的 MagicFunc:
function MagicFunc(obj) {
function FakeConstructor() {
this.x = 0; // StoreIC(x)
}
// 拷贝 <Map> 作为 prototype, 不再与其它对象共享
FakeConstructor.prototype = obj;
// StoreIC(x) state == UNINITIALIZED
new FakeConstructor();
// StoreIC(x) state == PREMONOMORPHIC
new FakeConstructor();
// StoreIC(x) state == MONOMORPHIC
// 隐式调用 MakePrototypesFast, 由 slow 到 fast 迁移
};
- FakeConstructor 构造器中的属性存取表达式引导 V8 创建一个 StoreIC(x) 。
- 将目标对象作为 FakeConstructor 的原型(prototype)。
- 第一次实例化 FakeConstructor 时 StoreIC(x) 由 初态 迁移到 单态前置,但由于 IC 的初始状态为 初态 故 V8 不会优化目标对象。
- 第二次实例化 FakeConstructor 时 StoreIC(x) 由 单态前置 迁移到 单态,由于 IC 的初始状态为 单态前置 故 V8 会隐式调用 MigrateSlowToFast 优化目标对象。
仅把对象设置成为另外一个函数(对象)的原型(并不实例化或调用)时,此对象的 <Map> 会被拷贝,不再与其它(相同构建结构的)对象共享一个 <Map> 。
// flags: --allow-natives-syntax
var a = {x : 1};
var b = {x : 1};
%HaveSameMap(a, b); // true, 由于它们拥有相同构建结构
var FakeConstructor = function() {};
FakeConstructor.prototype = b;
%HaveSameMap(a, b); // false, 由于 b 被设置成为了 FakeConstructor 的 prototype
通过 Runtime Call (%HaveSameMap) 来判断两个 JSObject<Map> 是否相同。
我们发现, V8 隐式调用 MigrateSlowToFast 的路径,不仅可以通过创建 StoreIC(也就是 MagicFunc)的方法实现,还可以通过创建 LoadIC 来实现。
下面就是其一种实现:
function MagicFunc_Alt(obj) {
var fakeObj = {};
function FakeLoad() {
fakeObj.x; // LoadIC(x), 即使 x 不存在
}
// CopyAsPrototype
fakeObj.__proto__ = obj;
// LoadIC(x) state == UNINITIALIZED
FakeLoad();
// LoadIC(x) state == PREMONOMORPHIC
FakeLoad();
// LoadIC(x) state == MONOMORPHIC
// 隐式调用 MakePrototypesFast, 由 slow 到 fast 迁移
}
原理与效果同 MagicFunc 类似就不重述了。
了解完它们的原理后,我们还可以写出很多类似的 MagicFunc ,所以使用奇技淫巧,不仅要知其然,还需知其所以然,才能举一反三,就算 V8 更新也可以找出解决方案。
在实际业务中,有时 delete 操作难以避免,有没有办法删除属性又不让对象退化到字典模式(而不用退化之后再去优化了)?欢迎收看下一章: