Home > Back-end >  Polymorphism overwhelming inline caches
Polymorphism overwhelming inline caches

Time:11-30

How I understand inline caches, there is no check for "or further down in the hidden class tree" (would be costly to do without having some trick). Even though an instance property from a base class would always be at the same offset, an access of a base class property encountering many different subclass instances would mean its inline cache gets overwhelmed (or does it? Perhaps I am wrong here already, strongly typed languages usually do cover this case, perhaps there is a trick being used?). This also would apply to objects in general.

This left me wondering: instead of subclasses, wouldn't it sometimes be better, to have two objects, one with the base properties, one with the subclass properties, both having a reference to the other?

Class extension version:

class A {
  x = 0;
}
class B1 extends A {
  y = 0;
}
class B2 extends A {
  z = 0;
}
const b1 = new B1();
const b2 = new B2();

const f = (p: A) => { p.x; };
// Different hidden classes.
f(b1);
f(b2);

Linked objects version:

class A<T> {
  x = 0;
  extension: T;
  constructor(extension: T) { this.extension = extension; }
}
class B1 {
  a: A<B1>;
  y = 0;
  constructor() { this.a = new A(this); }
}
class B2 {
  a: A<B2>;
  z = 0;
  constructor() { this.a = new A(this); }
}
const b1 = new B1();
const b2 = new B2();
const a1 = b1.a;
const a2 = b2.a;

const f = <T,>(p: A<T>) => { p.x; };
// Same hidden class.
f(a1);
f(a2);

Does this have any tangible performance impact? I guess the answer is always "don't care until you measured it's your bottleneck", but I am left wondering.

CodePudding user response:

In short: Yes.

Subclassing can lead to excessive polymorphism when an engine uses hidden classes and equality comparisons on these hidden classes (like V8 does). (And for the record, that doesn't imply that hidden classes are a bad idea -- they just happen to have both benefits and limitations, like any engineering technique.)

Modifying your object layout can be a good way to avoid that. How exactly to do that depends a lot on your requirements. You may or may not need the bidirectional links; e.g. a variant that might be good enough to address some common usage scenarios is:

class A {
  x = 0;
  extension;
  constructor(extension) { this.extension = extension; }
}

function MakeB1() {
  return new A({y: 0});
}
function MakeB2() {
  return new A({z: 0});
}
// And then the rest is very similar to your first example, just
// with "MakeB1" instead of "new B1":
const b1 = MakeB1();
...
f(b1);

You may want to include a type property in class A, then you can do things like
if (a.type == B1) ProcessB1(a) else if (a.type == B2) ProcessB2(a);.

Does this have any tangible performance impact?

It might, sometimes...

I guess the answer is always "don't care until you measured it's your bottleneck"

...absolutely. Many/most apps don't need to care, but for certain performance-sensitive code, optimizations like these might help quite a bit. Only profiling can tell you which parts of your app might be worth optimizing.

  • Related