Open Bug 2038057 Opened 3 months ago Updated 3 months ago

Make base constructors more monomorphic

Categories

(Core :: JavaScript Engine: JIT, enhancement, P2)

enhancement

Tracking

()

People

(Reporter: iain, Unassigned)

References

(Blocks 1 open bug)

Details

Attachments

(1 file)

In the FlightPlanner subtest of JS3, there is a Leg class with many subclasses (eg StartLeg, ClimbLeg, TurnLeg, ...). The Leg constructor initializes 20 properties. We generate a megamorphic store for each of those properties. Each derived class has a different prototype chain, and therefore a different shape, and the Leg constructor sees all those shapes.

Here's a microbenchmark targeted at the general pattern:

class Base {
  constructor() {
    this.a = 0; this.b = 0; this.c = 0; this.d = 0;
    this.e = 0; this.f = 0; this.g = 0; this.h = 0;
    this.i = 0; this.j = 0; this.k = 0; this.l = 0;
    this.m = 0; this.n = 0; this.o = 0; this.p = 0;
    this.q = 0; this.r = 0; this.s = 0; this.t = 0;
    this.u = 0; this.v = 0; this.w = 0; this.x = 0;
    this.y = 0; this.z = 0;
  }
}

class S1 extends Base { constructor() { super(); } }
class S2 extends Base { constructor() { super(); } }
class S3 extends Base { constructor() { super(); } }
class S4 extends Base { constructor() { super(); } }
class S5 extends Base { constructor() { super(); } }
class S6 extends Base { constructor() { super(); } }

class S1S extends S1 { constructor() { super(); } }
class S2S extends S2 { constructor() { super(); } }
class S3S extends S3 { constructor() { super(); } }
class S4S extends S4 { constructor() { super(); } }
class S5S extends S5 { constructor() { super(); } }
class S6S extends S6 { constructor() { super(); } }

let classes = [Base, S1, S2, S3, S4, S5, S6, S1S, S2S, S3S, S4S, S5S, S6S]
let fns = classes.map((C) => () => {return new C});

function foo() {
  let result = undefined;
  for (var i = 0; i < 500000; i++) {
    result = fns[i % classes.length]();
  }
  return result;
}

let start = performance.now();
foo();
print(performance.now() - start)

On this particular benchmark, we're nearly twice as slow as JSC, and nearly 3x V8.

It would be nice if we could make this more monomorphic. One option would be to try to extend the work in bug 1999828 to also support (eg) AddAndStoreSlotFromOffset, but that starts to get more than a little hairy. Perhaps a nicer approach is to use aggressive trial inlining for SuperCall, so that each class has its own set of private ICScripts for the proto chain, and they can all be monomorphic. Unfortunately, a naive implementation of this approach runs into a problem with calls from the C++ interpreter and/or the DoCallFallback code. Until the leaf class is actually using an IC to call its superclass, it will still pollute its superclasses with its own type, making them megamorphic and potentially impeding future inlining.

My hacked-together prototype got a 5% win on FlightPlanner, which works out to a 0.06% improvement in JS3. If we can find a way to plug the leak and give all these classes a fresh ICScript, though, I think we could do a bit better. My prototype is as fast on V8 on the microbenchmark, despite still having obvious room for improvement. I'll attach it here as a reference.

The problem with the patch I've attached is that it doesn't handle calls that don't go through the IC: that is, calls from the C++ interpreter before we've tiered up to blinterp, and the first call from the DoCallFallback code (because we attach an IC but don't use it until the second time around).

We could theoretically address the first problem by eagerly tiering up constructor calls to blinterp, so that they never run in C++. It doesn't seem prima facie unreasonable to assume that a constructor is likely to be called an above-average number of times.

To fix the second problem, we could consider rewriting some of our stub fallback code to use the IC when possible instead of doing the call via C++. Jan and I had already discussed doing this for general performance reasons. The highest value opportunities are probably GetProp and Call. (We can't do it for all ICs, because a) sometimes we don't attach a stub, and b) for some ops, like Unary/BinaryArith ops, we always perform the operation in C++ before attaching an IC so that we can make decisions based on the result type.)

If we can plug both of those holes, I think eagerly inlining SuperCall would work to make things monomorphic.

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: