Skip to content

Instantly share code, notes, and snippets.

@robinheghan
Created June 25, 2021 10:19
Show Gist options
  • Save robinheghan/3af41c98177a333ed81b0dee875f6041 to your computer and use it in GitHub Desktop.
Save robinheghan/3af41c98177a333ed81b0dee875f6041 to your computer and use it in GitHub Desktop.
Questions about JS engine fundamentals

Based on:

Shapes

So these two objects will have different shapes:

function ObjA(a, b) {
  this.a = a;
  this.b = b;
}

function ObjB(a, b) {
  return {
    a: a,
    b: b
  };
}

// shape: { b } -> { a } -> {}
var a = new ObjA(1, 2);

// shape: { a, b }
var b = ObjB(1, 2);

But will these objects have the same shape?

function ObjA(a, b) {
  this.a = a;
  this.b = b;
}

function ObjB(a, b) {
  this.a = a;
  this.b = b;
}

var a = new ObjA(1, 2);
var b = new ObjB(1, 2);
  1. So the fact that they're different classes doesn't mean anything to the js engine?
  2. If I were to add a function to ObjB's prototype, would that result in ObjA and ObjB instances having different shapes?
  3. From a performance perspective, are object literals "better" as long as you don't plan to use the prototype, or doesn't it matter?

Megamorphic functions

Say I have the following function:

function biggerThanFive(obj) {
  var some_val = obj.getValue();
  if (some_val > 5) {
    return true;
  } else {
    return false;
  }
}

Imagine that during the lifetime of my application, I pass 100 objects with different shapes to biggerThanFive. This would result in biggerThanFive being megamorphic.

  1. Am I correct in assuming that this function wouldn't be optimized at all?
  2. Would this function still be megamorphic even though all these objects has getValue stored on their prototype objects, and that the prototype objects are all the same shape?
  3. Would it be possible to limit megamorphic code by dividing megamorphic pieces into their own function? Like this:
// monomorphic?
function biggerThanFive(obj) {
  var some_val = getValue(obj);
  if (some_val > 5) {
    return true;
  } else {
    return false;
  }
}

// megamorphic?
function getValue(obj) {
  return obj.getValue();
}
@mathiasbynens
Copy link

Re: the first question, see this part of the article on prototypes: https://mathiasbynens.be/notes/prototypes#:~:text=and%20that%E2%80%99s%20essentially%20what%20engines%20do%20with%20a%20simple%20trick%3A%20instead%20of%20storing%20the%20prototype%20link%20on%20the%20instance%20itself%2C%20engines%20store%20it%20on%20the%20shape.

"And that’s essentially what engines do with a simple trick: instead of storing the prototype link on the instance itself, engines store it on the Shape. […] Each shape points to the prototype. This also means that every time the prototype of foo changes, the engine transitions to a new shape. Now we only need to check the shape of an object to both assert absence of certain properties and also guard the prototype link."

For the other questions involving the very specific examples, I’ll do you one better by telling you how you can find the answer yourself :) Grab a d8 binary (jsvu is the easiest way to do it) and run it with the --trace-ic flag on any given *.js file. Benedikt’s blog post at https://benediktmeurer.de/2018/03/23/impact-of-polymorphism-on-component-based-frameworks-like-react/ walks through an example you could use as a basis. This allows you to figure out which IC loads are uninitialized, premonomorphic, monomorphic, polymorphic, or megamorphic — for any code snippet you want to test!

Hope this helps.

@robinheghan
Copy link
Author

Thank you for the links, and for taking the time! 😊

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment