Why do monomorphic and polymorphic matter in JavaScript?

6.5k views Asked by At

I've been reading some articles on change detection, and all of them say that monomorphic functions are much faster than polymorphic. For example, here is a quote:

(..)The reason for that is, that it has to be written in a dynamic way, so it can check every component no matter what its model structure looks like. VMs don’t like this sort of dynamic code, because they can’t optimize it. It’s considered polymorphic as the shape of the objects isn’t always the same. Angular creates change detector classes at runtime for each component, which are monomorphic, because they know exactly what the shape of the component’s model is. VMs can perfectly optimize this code, which makes it very fast to execute. The good thing is that we don’t have to care about that too much, because Angular does it automatically.(..)

Source

Now, I was trying to find examples of monomoprhic vs polymorphic, but couldn't find it anywhere. Could anyone care to explain the difference, and why is it faster?

2

There are 2 answers

0
Klemen Slavič On BEST ANSWER

The answer lies in the fact that VMs can do heuristic detection of "hot functions", meaning code that is executed hundreds or even thousands of times. If a function's execution count exceeds a predetermined limit, the VMs optimizer might pick up that bit of code and attempt to compile an optimized version based on the arguments passed to the function. In this case, it presumes your function will always be called with the same type of arguments (not necessarily the same objects).

The reason for this is well-documented in this v8-specific guideline document where an integer vs. general number optimization is explained. Say you have:

function add(a, b) { return a + b; }

...and you're always calling this function with integers, this method might be optimized by compiling a function that does integer summation on the CPU, which is fast. If after optimization you feed it a non-integer value, then the VM deoptimizes the function and falls back to the unoptimized version, since it cannot perform integer summation on non-integers and the function would return erroneous results.

In languages where you specify overloaded monomorphic methods you can get around this problem by simply compiling multiple versions of the same method name with different argument signatures which are then optimized on their own. This means that you call different optimized methods because using differenty typed arguments requires you to use a different overloaded method, so there's no question of which method you're using.

You might think that you could keep multiple copies of optimized functions in the VM and check types to determine which optimized compiled function to use. In theory, that would work, if type checking before method invocation were free or very inexpensive. In practice, that usually doesn't turn out to be the case, and you'd probably want to balance things against real-world code to determine the best tradeoff threshold.

Here's a more generalized explanation v8's optimizing compiler in particular (from Google I/O 2012):

https://youtu.be/UJPdhx5zTaw?t=26m26s

In short: functions that are invoked with the same types over and over again are optimized in the JIT compiler, hence faster.

0
Frank Bryce On

To my knowledge, monomorphism is a very uncommon term. I've personally never heard it used for coding. To figure out what monomorphism is, though, I think we can infer what it means by looking at what polymorphism is.

Polymorphism: is the idea that many (poly-) different objects can be represented by the same type to the machine/runtime/interpreter. For instance, in C#, you may have as many classes as you want that implement ICloneable and any of them could be used in a copy constructor for a generic linked list (for example). Full un-tested class here as an example if you're interested

Ok so what does monomorphic mean?

Monomorphic to me means that the interpreter of the object handles the EXACT type that it is expecting, and no inheritance or modifications to the type expected is possible. In this context, with the duck typed javascript language, the VM says "this javascript object has these exact properties which are of these exact types and named exactly like they are". In C#, if we wanted to be monomorphic, then generic type restrictions would be impossible because T would have to be the same type all the time.

This link provides a good guide as to why this matters to performance. To me this is can be summed up below.

Javascript engines would like to avoid table lookups for properties, and instead to do object pointer offsets. With monomorphism, the object offsets for objects in a given line of code will always be the same and the VM has an easy time figuring out how to perform lookups with pointer addition rather than table lookups.

In reality, the engines try to handle a small amount of different objects being passed to the same function but the VM will be fastest if the object always looks the same on the same line of code.

Example for clarity

The following example is valid javascript, but the argument o to function f1 is NOT monomorphic, because the VM needs to handle two differently shaped objects being passed in.

function f1(o) {
  console.log(o.prop1)
  console.log(o.prop2)
}

// ...

o1 = { prop1: 'prop1', prop2: 'prop2' }
o2 = { prop1: 'prop1', prop2: 'prop2', prop3: 'prop3' }

f1(o1)
f1(o2)

The point of the quote from the link that you provided is that AngularJS out of the box provides code which makes all of its javascript function arguments to be "monomorphic" because the objects passed into them happen to have the same structure every time they are called.