Performance. First of all, when we use the word performance in the context of a computer program, we are referring to how quickly or efficiently that program can execute.
Polymorphic functions. A polymorphic function is a function that changes its behavior based on the types of arguments that are passed to it.
The key word here is types, as opposed to values. (A function that didn’t change its output based on different values for arguments would not be a very useful function at all.)
Option 1: Use completely separate arguments
The first signature of our function will take the required data as three separate arguments, and can be called like this:
Option 2: Use message contents with
This signature will allow consumers to separate the required data (message contents) from the optional data (the author and the timestamp) into two separate arguments. We’ll accept the arguments in any order, for convenience.
Option 3: Use an
We’ll also allow users of our API to call the function passing in a single argument of an object containing all of the data we need:
Option 4: Use only the message contents
Finally, we’ll allow users of our API to provide only the message contents, and we’ll provide default values for the rest of the data:
Implementing a polymorphic function
OK, with our API defined, we can build the implementation of our polymorphic function.
OK, now we’ll write some code that stores a lot of messages using our function — taking advantage of its polymorphic API — and measure its performance.
Now let’s implement our function again but with a simpler, monomorphic API.
In exchange for a more restrictive API, we can trim down the complexity of our function and make it monomorphic, meaning that the arguments of the function are always of the same type and in the same order.
Although it won’t be as flexible, we can keep some of the ergonomics of the previous implementation by utilizing default arguments. Our new function will look like this:
We’ll update the performance measuring code from our previous example to use our new unified API.
OK, now let’s run our programs and compare the results.
The monomorphic version of our function is about twice as fast as the polymorphic version, as there is less code to execute in the monomorphic version. But because the types and shapes of the arguments in the polymorphic version vary widely, V8 has a more difficult time making optimizations to our code.
In simple terms, when V8 can identify (a) that we call a function frequently, and (b) that the function gets called with the same types of arguments, V8 can create “shortcuts” for things like object property lookups, arithmetic, string operations, and more.
For a deeper look at how these “shortcuts” work I would recommend this article: What’s up with monomorphism? by Vyacheslav Egorov.
Pros and cons of polymorphic vs monomorphic functions
Before you go off optimizing all of your code to be monomorphic, there are a few important points to consider first:
Polymorphic function calls are not likely to be your performance bottleneck. There are many other types of operations that contribute much more commonly to performance problems, like latent network calls, moving large amounts of data around in memory, disk i/o, complex database queries, to name just a few.
You will only run into performance issues with polymorphic functions if those functions are very, very “hot” (frequently run). Only highly specialized applications, similar to our contrived examples above, will benefit from optimization at this level. If you have a polymorphic function that runs only a few times, there will be no benefit from rewriting it to be monomorphic.
In many cases, however, the difference will be insignificant. API patterns should be based on other factors like legibility, consistency, and maintainability because performance issues are more likely to crop up in other areas anyway. Happy coding!