DEV Community

MOYED
MOYED

Posted on

11. JavaScript Engines

Articles


JS Engine

JS engine is a program that converts JS code into lower level or machine code that microprocessors can understand. The goal of JS engine is to generate the most optimzied code in shortest time possible.

Uniqueness of JS engine

One interesting feature of Javascript is that JS is dynamic typing. It means that we don't have to specify the type of variable when we declare it.

let number = 17;
Enter fullscreen mode Exit fullscreen mode

We didn't declared the variable number as an integer or number type. But JS engine dynamically converts it as a number as a machine code. So how does JS engine work?


How it works

  • First, JS engine parses the source code, and generates the Abstract Syntax Tree(AST).

  • Then, based on AST, interpreter generates the bytecode and executes it.

  • While it executes the bytecode, if the function is 'hot', which means that it is used multiple times, it sends the profiling data to the optimizing compiler.

  • Based on the data from previous executions, optimizing compiler generates the optimized code which is executed faster than bytecode.

  • If the assumption is changed, it de-optimizes and goes back to interpreter.

JIT(Just in time) Compilation

Often we call the JS engine does JIT compilation. It means that JS engine generates machine code during the run time, not ahead of time(AOT). So because of this, JS engine understands even we don't specify the type of variablesor objects. As I mentioned, JS engine compiles and executes together with the help of interpreter & optimizing compiler.

On the other hand, in C++, C++ engine compiles and then executes. Which means that we should specify the type of varible, like this.

int number = 17;
Enter fullscreen mode Exit fullscreen mode

V8

In V8, interpreter is called 'ignition' and optimizing compiler is called 'turbofan'. This is an example of how V8 engine works in given source code.

let result = 0;
for (let i = 0; i < 4242424242; ++i) {
    result += i;
}
console.log(result);
Enter fullscreen mode Exit fullscreen mode

V8 starts to run the soruce code with ignition and starts to generate and execute the bytecode. When the engine notice its 'hot'(because the same function is repeated over time), turbofan frontend starts to generate profiling data of given function and sends it to the turbofan. Turbofan starts to generate optimized code.


Different types of JS engines

There are lots of different types of JS engines according to browsers.

It's good to have numerous JS engines because these engines would compete and eventually become better as time goes.

Why do they differ?

But why are engines different each other? Because there isn't a sole best solution. As I said earlier, the ultimate goal of JS engine is to generate the most optimized code as fast it can. In fact, there is trade-off between generating code quickly, and executing code quickly.

So some engines with more optimzing tiers, tend to focus on executing fast, while it takes long time to generate it. And engines with less optimizing tiers focuses on generating code quickly, while it takes more time to execute because it is less optimized

There's another trade-off that JS engines consider.

More optimization takes more memory. So, trade-off between optimization and memory usage should be also considered.


Object Optimization

Objects are just dictionaries which key is string type. String kyes are mapped to something called "property attributes".

Property attributes

  • Value: the value retuned by accessing property ex) object.x

  • Writable: whether it can be reassigned

  • Ennumerable: whether it can be used in loops

  • Configurable: whether it is deleteable

We can get Property attributes by Object.getownPropertydescript API.

Problem

The problem of storing object this way is that if there are lots of objects, we should allocate memories for each objects, which is wasteful. So, JS engine uses unique mechanism for handling objects.


Shapes

If objects has same properties, we call that objects have same 'shapes'.(shapes is synonym to hidden clases, map, structure.)

let object1 = {
  x: 1,
  y: 2
}

let object2 = {
  x: 5,
  y: 6
}
Enter fullscreen mode Exit fullscreen mode

Object1 and object2 has same shape.

JS engine uses this concept called shape internally, to handle objects in more optimized way.

Object <-> Shape

In object, only the values are stored and other property attributes are stored in shape. In shape, instead of the value, offset property is mapped to property of object. offset is the index where we can find the value according to the property. For example, for property x, we can find the value is the 0th place in object.

Same shape objects

In above exmaple, a and b has same shape. Instead of storing each property attributes to each objects, we store property attributes except value into shape. Using this mechanism, for 1000 objects for same shape, we need only one shape.which can save memory space.


Adding property to object

What happens when we start with certain shape and add properties?

let object = {};
object.x = 5;
object.y = 6;
Enter fullscreen mode Exit fullscreen mode

Something called transition chain occurs.

transition chain

New shapes are introduced when we add new properties. If we want to find the value of propert x, we walk through the chain until we find the shape that has property x. Then we look for the offset, which is 0. 0th value of Object o is 5. So, o.x = 5.

Still slow...

However, transition chain is still slow if there are multiple shapes included in chain. Time to find the value of property is O(n). So, to make it faster, we introduce ShapeTable. Shapetable is a dictionary which property maps to the corresponding shape.

So, we are back to dictionaries. So why use shape if it's just more abstract?


IC, Inline Caches

Here comes the IC. Ic is ingredient to make JS run fast and the main motivation for having shapes.

How ICs work?

JS engine use ICs to memorize information on where to find properties on objects which can reduce the number of lookups.

As we run the above code, interpreter generates the bytecode. Inline Caches are stored in get_by_id and has two slots which are uninitialized.

As we execute the code with the given object, get_by_id looks up the property x and finds the offset 0 and looks for the value.

After we execute, IC stores and maps the shape and offset which the property was found.

For same shape objects, with ICs, JS engine just first compares the shape, and load the value from memorized offset.


Takeaways

  1. Always initialize your object same shape as possible as you can. It boosts the optimization

  2. JS engine generates machine code in runtime.

Top comments (0)