I ran into this strange scenario where overriding a property getter is severely impacting performance (which will only be noticeable with lot of computation like sorting in the example below).
In the following example code, I have 3 classes
, VP
, F1
and F2
. F1
has a property called #vp
with a corresponding getter that returns this private member. F2
extends F1
and overrides the vp
getter which simply delegates to super.vp
. I actually don't need to do this but because I have to override the setter I also need to implement the getter as per JavaScript spec.
In the following code, I sort a million random numbers and the difference in performance using F1
object vs F2
object is around 1 second vs
5 seconds.
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);
At the moment I got away by getting rid of the setter/getter in F2 and instead using a separate setVP API to set the value. But I am curious as to why overriding the getter is making it so slow.
(V8 developer here.)
You didn't say which version of V8 you're using, so I have to guess a bit, which in this particular case is not too hard...
The reason for the slowdown you're seeing is not the fact that you're overriding the getter; it's the fact that you're using super.vp
in the overriding getter.
With Node 15.11 (V8 8.6) I can reproduce your results; with current V8 (9.0/9.1) I cannot. One difference between those versions is the work described here: https://v8.dev/blog/fast-super, which very much fits this scenario.
So if you simply update (to Chrome 90+, or current Node nightly builds which I guess will become Node 16) when updates are available, your code will magically speed up.
If you need a workaround that works today, find a way to avoid using super
on the hot path. For example by using a different name in the subclass, and getting to the inherited getter via regular property lookup, like so:
class F2 extends F1 {
#o;
set vp2(vp) { this.vp = vp; this.#o = 1; }
get vp2() { return this.vp; }
}
which I guess is similar to the "setVP API" you hinted at. Or you could stop using private fields and just access _vp
(instead of #vp
) directly from F2
, skipping the super accessor:
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; }
}