Photo by on

JIT Compilers

Anum Siddiqui
Jun 5, 2018 · 7 min read

So speaking of languages, we know in the coding world there are many. But how exactly, do these languages communicate with the computer. The goal generally is that we want to tell the computer what to do and the issue with that is that simply put that humans and computer speak different languages. However, we’re in luck because JavaScript and other programming languages are designed for human cognition, not for machine cognition. So the goal of the JavaScript engine is to take your human language and turn it into something the machine understands. You may ask how does this go about? Well in programming, this translation generally happens in two ways by using either an interpreter or a compiler. The difference is with an interpreter the translation happens line-by-line, on the other hand a compiler works ahead of time to create the translation and writes it down.

As almost with anything in the programming world, there are pros and cons to each of these way of handling the translation.

Interpreter pros and cons:

Interpreters are fairly quick, in a sense that you can skip the compilation step, and just go straight to translating the first line and running it. Due to this reason, it may seem as if an interpreter would be a better fit for JavaScript. Therefore, browsers in the beginning used to use interpreters.

However the disadvantage of working with an interpreter is when you’re running the same code more than once, such as in when you’re in a loop. This would lead to the same translation over and over again.

Compliler pros and cons:

The compiler as opposed to interpreters take a bit more time to start up because it has to go through that compilation step at the beginning. However after that step, the code in the loops run faster because it doesn't need to repeat the translation for each pass through the loop.

Compilers are also different than interpreters whereby that it has more time to look at the code and make edits so that it will run faster. The edits done by compilers are known as optimizations. Interpreters do work during runtime, so it is unable to take much time during the translation phase to figure out these optimizations.

Just- in- time compilers:

Browsers started mixing compilers in as a way of getting rid of the interpreter’s inefficiency. Browsers mix in compilers but adding a new part to the JavaScript engine called monitor, or otherwise known as a profiler. What the monitor does is that it watches the code as it run, and makes a note of how many times it is run and what types are used. In the beginning, the monitor just runs everything through the interpreter. The same lines of code that are run a few times are segments of code that is called warm, and if they’re ran a lot, they’re called hot.

Baseline compiler :

When a function starts to get warm, the just- in- time (JIT) will send it off to be compiled and then store that compilation. Each line of the function is compiled to what is known as a “stub”. The stubs are indexed by line number and variable type. The monitor comes into play when it sees that the execution is hitting the same code again with the same variable types, it will just go ahead and make life easier by pulling out its compiled version.

The baseline compiler will make some of these optimizations and not all for the sake that it does not want to take too much time, and hold up the execution.

Optimizing compiler:

When a part of the code is hot the monitor will send it off to the optimizing compiler. The role of the optimizing compiler is that it will create another, even faster version of the function that will be stored. To make a faster version of the code, the optimizing compiler has to make some assumptions. An example of assumption it can make is objects created by a particular constructor have the same shape, and that they always have the same property names, and those properties are added in the same order, this way it can cut some corners based on that guess. The optimizing compiler uses the information the monitor gathered by watching code execution to make these assumptions. If for instance, something has been true for previous passed through a loop, the optimizing compiler will then assume it will proceed to be true.

However in JavaScript, nothing can be for sure, so the compiled code needs to do a check before it runs to see whether the assumptions are valid. If it is valid, then the compiled code runs, otherwise JIT assumes that it made the wrong assumptions and trashes the optimized code. In that case the executions goes back to the interpreter or baseline compiled version. The going back process is known is deoptimization or “bailing out”.

Optimizing compilers make code faster, but there are instances where they can cause unexpected performance problems. For example, if a code keeps getting optimized and then deoptimized, it ends up being slower compared to just executing the baseline compiled version.

Example of optimization: Type specialization:

There are a lot of kinds of optimizations, one in particular is known as type specialization. The dynamic type system that JavaScript uses requires extra work at runtime.

function arraySum(arr) {
var sum = 0;
for (var i = 0; i < arr.length; i++) {
sum += arr[i];
}
}

In this example, the += in the loop looks simple, and it sees that you can calculate this in one single step, but because of dynamic typic, it takes more steps than you would think. If we assume that the arr in this example is an array of 100 integers, once the code warms up, the baseline compiler will go ahead and create a stub for each operation in the function. So there is going to be a stub for sum+= arr[i] that will take care of the += operation as integer addition.

However, sum and arr[i] aren’t guaranteed to be integers. Since types are dynamic in JavaScript, there is a possibility that in later iteration of the loop arr[i] will be a string. Integer addition and string concatenation are different operations , therefore they would compile to different machine code.

The way JIT take care of this is by compiling multiple baseline studs. If a piece of code is monomorphic, meaning that its always called with the same types, then it will get one stub. But, it is polymorphic, where it is called with different types from one pass through the code to another, then it will get a stub for each combination type that passes through that operation. So for JIT, this will result it to ask a lot of questions before it chooses a stub. Since each line of code has its own set of stubs in the baseline compiler the JIT needs to keep checking the types each time the line of code is executed. So it will ask the same questions for each iteration through the loop.

The code would execute a lot faster if the JIT didn’t need to repeat those check, and that’s one of the things the optimizing compiler takes care of. In the optimizing compiler, the whole function is compiled together. The type checks are moved so they happen before the loop. Some JIT optimize this even further. For example, in Firefox there’s a special classification for arrays thats only contain integers. So if arr is one of these arrays, then JIT doesn’t need to check arr[i] is an integer. This means that the JIT can do all of the type checks before it enters the loop.

Takeaways:

In conclusion, JIT helps JavaScript run faster by monitoring the code as it’s running it and sending hot code paths to be optimized. This leads to many performance improvements for JavaScript applications. In addition, to make things faster, the JIT has overhead that it adds during runtime:

  • optimization and deoptimization
  • memory which is used for monitor to referenceand recovery information for when bailouts happen.
  • memory used in order to store baseline and optimizing versions of a function

Although even with these improvements, the performance of JavaScript can be unpredictable.

Anum Siddiqui

Written by

New York native Full Stack Web Developer on the hunt for new opportunities and always hungry to learn!

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade