node/v8 中被覆盖的 getter 性能下降

Degraded performance of a overridden getter in node/v8

我 运行 进入这个 st运行ge 场景,其中覆盖 属性 getter 会严重影响性能(只有在进行大量计算(如排序)时才会注意到在下面的示例中)。

在下面的示例代码中,我有 3 个 classesVPF1F2F1 有一个名为 #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; }
}