node/v8 中被覆盖的 getter 性能下降
Degraded performance of a overridden getter in node/v8
我 运行 进入这个 st运行ge 场景,其中覆盖 属性 getter 会严重影响性能(只有在进行大量计算(如排序)时才会注意到在下面的示例中)。
在下面的示例代码中,我有 3 个 classes
、VP
、F1
和 F2
。 F1
有一个名为 #vp
的 属性 和 returns 这个私有成员对应的 getter。 F2
扩展 F1
并覆盖 vp
getter,它只是委托给 super.vp
。我实际上不需要这样做,但是因为我必须覆盖 setter 我还需要根据 JavaScript 规范实施 getter。
在下面的代码中,我对一百万个 运行dom 数字进行排序,使用 F1
对象与 F2
对象的性能差异约为 1 秒 vs
5秒。
import { performance } from 'perf_hooks';
class VP {
#idx = 0;
getValue(row) { return row[this.#idx]; }
}
class F1 {
#vp;
set vp(vp) { this.#vp = vp; }
get vp() { return this.#vp; }
}
class F2 extends F1 {
#o;
set vp(vp) { super.vp = vp; this.#o = 1; }
get vp() { return super.vp; }
}
const vp = new VP();
const f1 = new F1();
f1.vp = vp;
const f2 = new F2();
f2.vp = vp;
function getData(size) {
const data = new Array();
for(let i=0;i<size;i++) data[i] = [Math.random()*1000000];
return data;
}
const data = getData(1000000);
const t1 = performance.now();
// data.sort((a,b) => f1.vp.getValue(a) - f1.vp.getValue(b)); // 940ms
// data.sort((a,b) => { let vp = f1.vp; return vp.getValue(a) - vp.getValue(b); }); // 945ms
data.sort((a,b) => f2.vp.getValue(a) - f2.vp.getValue(b)); // 4990ms
// data.sort((a,b) => { let vp = f2.vp; return vp.getValue(a) - vp.getValue(b); }); // 3146ms
const t2 = performance.now();
console.debug(t2-t1);
目前,我摆脱了 F2 中的 setter/getter,而是使用单独的 setVP API 来设置值。但我很好奇为什么覆盖 getter 会使其变得如此缓慢。
(此处为 V8 开发人员。)
你没有说你使用的是哪个版本的 V8,所以我不得不猜测一下,在这种特殊情况下这并不难...
您看到的减速的原因并不是您覆盖了 getter;事实上,您在 getter.
中使用了 super.vp
使用 Node 15.11 (V8 8.6) 我可以重现您的结果;使用当前的 V8 (9.0/9.1) 我不能。这些版本之间的一个区别是此处描述的工作:https://v8.dev/blog/fast-super,非常适合这种情况。
因此,如果您在更新可用时简单地更新(到 Chrome 90+,或当前的 Node 每晚构建,我猜将成为 Node 16),您的代码将神奇地加速。
如果您需要一种在今天 有效的解决方法,请找到避免在热路径上使用 super
的方法。例如,通过在子类中使用不同的名称,并通过常规 属性 查找获取继承的 getter,如下所示:
class F2 extends F1 {
#o;
set vp2(vp) { this.vp = vp; this.#o = 1; }
get vp2() { return this.vp; }
}
我猜这与您暗示的“setVP API”类似。或者您可以停止使用私有字段并直接从 F2
访问 _vp
(而不是 #vp
),跳过超级访问器:
class F1 {
_vp;
set vp(vp) { this._vp = vp; }
get vp() { return this._vp; }
}
class F2 extends F1 {
#o;
set vp(vp) { this._vp = vp; this.#o = 1; }
get vp() { return this._vp; }
}
我 运行 进入这个 st运行ge 场景,其中覆盖 属性 getter 会严重影响性能(只有在进行大量计算(如排序)时才会注意到在下面的示例中)。
在下面的示例代码中,我有 3 个 classes
、VP
、F1
和 F2
。 F1
有一个名为 #vp
的 属性 和 returns 这个私有成员对应的 getter。 F2
扩展 F1
并覆盖 vp
getter,它只是委托给 super.vp
。我实际上不需要这样做,但是因为我必须覆盖 setter 我还需要根据 JavaScript 规范实施 getter。
在下面的代码中,我对一百万个 运行dom 数字进行排序,使用 F1
对象与 F2
对象的性能差异约为 1 秒 vs
5秒。
import { performance } from 'perf_hooks';
class VP {
#idx = 0;
getValue(row) { return row[this.#idx]; }
}
class F1 {
#vp;
set vp(vp) { this.#vp = vp; }
get vp() { return this.#vp; }
}
class F2 extends F1 {
#o;
set vp(vp) { super.vp = vp; this.#o = 1; }
get vp() { return super.vp; }
}
const vp = new VP();
const f1 = new F1();
f1.vp = vp;
const f2 = new F2();
f2.vp = vp;
function getData(size) {
const data = new Array();
for(let i=0;i<size;i++) data[i] = [Math.random()*1000000];
return data;
}
const data = getData(1000000);
const t1 = performance.now();
// data.sort((a,b) => f1.vp.getValue(a) - f1.vp.getValue(b)); // 940ms
// data.sort((a,b) => { let vp = f1.vp; return vp.getValue(a) - vp.getValue(b); }); // 945ms
data.sort((a,b) => f2.vp.getValue(a) - f2.vp.getValue(b)); // 4990ms
// data.sort((a,b) => { let vp = f2.vp; return vp.getValue(a) - vp.getValue(b); }); // 3146ms
const t2 = performance.now();
console.debug(t2-t1);
目前,我摆脱了 F2 中的 setter/getter,而是使用单独的 setVP API 来设置值。但我很好奇为什么覆盖 getter 会使其变得如此缓慢。
(此处为 V8 开发人员。)
你没有说你使用的是哪个版本的 V8,所以我不得不猜测一下,在这种特殊情况下这并不难...
您看到的减速的原因并不是您覆盖了 getter;事实上,您在 getter.
中使用了super.vp
使用 Node 15.11 (V8 8.6) 我可以重现您的结果;使用当前的 V8 (9.0/9.1) 我不能。这些版本之间的一个区别是此处描述的工作:https://v8.dev/blog/fast-super,非常适合这种情况。
因此,如果您在更新可用时简单地更新(到 Chrome 90+,或当前的 Node 每晚构建,我猜将成为 Node 16),您的代码将神奇地加速。
如果您需要一种在今天 有效的解决方法,请找到避免在热路径上使用 super
的方法。例如,通过在子类中使用不同的名称,并通过常规 属性 查找获取继承的 getter,如下所示:
class F2 extends F1 {
#o;
set vp2(vp) { this.vp = vp; this.#o = 1; }
get vp2() { return this.vp; }
}
我猜这与您暗示的“setVP API”类似。或者您可以停止使用私有字段并直接从 F2
访问 _vp
(而不是 #vp
),跳过超级访问器:
class F1 {
_vp;
set vp(vp) { this._vp = vp; }
get vp() { return this._vp; }
}
class F2 extends F1 {
#o;
set vp(vp) { this._vp = vp; this.#o = 1; }
get vp() { return this._vp; }
}