2

All articles I found about V8 and Monomorphism / Polymorphism just mention that the shape of an object should stay the same, so the JIT compiler can optimize it as a monomorphic function. But what about similar shaped objects like you get with OOP and inheritance?

I'm interested in the following pretty common OOP case, where you have a bunch of similar shaped objects, like when having a class hierarchy, where classes (and their objects) share common properties. For example:

obj1 = {
  foo: 42,
  bar: true
}

obj2 = {
  baz: "abc",
  foo: 666,
  qux: null
}

Now when having some function that only uses the foo property, I can pass all those different objects to it. From the function's perspective they all have the same shape { foo: number } + *, but their real shape of course is different due to the additional properties bar, baz and qux.

Is such a case somehow covered by the JIT compiler?

And if not, how to refactor similar shaped objects to fit the "monomorphic" pattern?

Benjamin M
  • 23,599
  • 32
  • 121
  • 201
  • 1
    Please put a question in the title, not a list of buzzwords/tags. – Bergi May 03 '23 at 21:56
  • "*the shape of an object should stay the same, so the JIT compiler can optimize it as a monomorphic function*" - a function is a separate thing from the objects it is called on. Please don't confuse them. And that a single object should not change its shape has yet a different reason. – Bergi May 03 '23 at 21:58

1 Answers1

2

(V8 developer here.)

Short answer: "same" != "similar".

From the function's perspective they all have the same shape { foo: number } + *

No, that's very much not what "the same shape" means. "The same shape" doesn't mean "they all have a foo property somewhere". "The same shape" means, for example, "they each have exactly three properties foo, bar, baz, in that order". (The actual internal definition includes more, but that's the upshot.)

Monomorphic code is fast because it can always do the same thing, for example always load the first property. Whereas when it has to decide what to do every time ("oh, this is one of the objects where foo is the second property... oh, this is an object with a shape I've never seen before, let's see if it has a foo property at all and where that might be..."), then all that figuring-out-what-to-do costs time.

If it helps your mental model, you can compare this with physical work. When a factory produces exactly one car model, you can build a simple and fast robot that tightens the exact same screw on every car, which it always finds in the exact same spot. Whereas in a garage that repairs arbitrary old cars, a robot that's supposed to tighten their screws first has to figure out where the screws even are in this particular car that it's looking at, and how to get to them. It doesn't help the robot that all cars are "similar" in that they each have a loose screw somewhere; what makes the robot's life hard is that each car is different.

Is such a case somehow covered by the JIT compiler?

Sure, it's more work under the hood, but it works just fine, and usually it's fast enough. Don't worry about premature optimizations when you don't have a performance problem.

how to refactor similar shaped objects to fit the "monomorphic" pattern?

Make the objects have exactly the same shape. For example, construct them all with the same constructor, and then don't add any properties afterwards. (And, of course, don't make some of the properties that the constructor adds conditional either, that would defeat the point.)

With class hierarchies, polymorphism is typically hard or impossible to avoid. See here for a related question.

jmrk
  • 34,271
  • 7
  • 59
  • 74
  • Couldn't the compiler generate optimised code for objects that all have `foo` as their first property and that is as fast as monomorphic code, even if they have different additional properties (and therefore different shapes)? – Bergi May 03 '23 at 22:43
  • Thanks for the detailed response! It's not about premature optimization, it's pure interest. I already understood the basic concept. I was mainly wondering how exact "same" was meant. It could have been that for example the called function is introspected, and looked at what properties are actually used, and then ignore all other properties. .. Or maybe a hierarchy or frozen classes, so the compiler can be sure that all objects of those classes have always the same properties in the same order. – Benjamin M May 03 '23 at 22:43
  • @Bergi: checking for more than one hidden class means it's polymorphic. Whether the different classes are then handled by shared code or not (I think they are) is a minor code-saving optimization. The key part is that you _don't_ want to perform a property lookup every time, that'd be slow. – jmrk May 04 '23 at 08:58
  • @BenjaminM very very few things in JavaScript can be relied upon to be frozen (for example: class hierarchies are never frozen; there are no guarantees about all objects in the system having or not having a given characteristic; a function can always be called from another caller or with different arguments; etc). I'm also not sure what you even mean by "ignoring properties that aren't used" -- of course properties that aren't used are ignored, but that doesn't make anything faster. – jmrk May 04 '23 at 09:08
  • By "ignoring unused properties" I meant someting like this: if the compiler somehow knows that a given function only uses properties `a` and `b` of an argument, then only the two types of those two properties have to be checked. This way it wouldn't matter if it is called with `{a,b}` or `{a,b,c}`, because everything except from `a` and `b` can be ignored. I imagined something like a prefix (`props.slice(0, 2)`) to keep the shape the same. But I guess that would be quite expensive, as it might involve dynamic filtering/reordering of the properties, depending on which function gets called – Benjamin M May 04 '23 at 12:07
  • @BenjaminM: Yes, way too expensive. Also, a JS engine can't reorder objects' properties, because that would change semantics (in other words, that would be a spec violation, and would quite likely break some programs). – jmrk May 04 '23 at 12:30