JavaScript Engines and Their Optimization Techniques
Passionate coder on an exciting journey to master JavaScript and web development. Constantly learning, building, and sharing my experiences as I grow in the tech world .
Introduction to JavaScript Engines
Today, I learned that there are different engines in which we can run our JavaScript code, and we can install all these engines using jsvu (JavaScript (engine) Version Updater). Here are the different types of engines:
Chakra (
chakraorch)GraalJS (
graaljs)Hermes (
hermes&hermes-repl)JavaScriptCore (
javascriptcoreorjsc)QuickJS-ng (
quickjs)SpiderMonkey (
spidermonkeyorsm)V8 (
v8)V8-Debug (
d8)XS (
xs)
Installing JavaScript Engines
You can install all these engines using the command:
npm install jsvu -g
However, this command only works up to the V8 engine. For version 9 of the engine, you have to use the command:
jsvu --os=linux64 --engines=all
This is equivalent to:
jsvu --os=linux64 --engines=chakra,graaljs,hermes,javascriptcore,quickjs,spidermonkey,v8,xs
When I installed the V8-Debug engine and created a file containing an array:
const arr = []; %DebugPrint(arr);
When I ran this code on V8-Debug, it provided different types of information that we can't get in the V8 engine. Some of this information includes: type, instance size, element kind, enum length, back pointer, instance description, transition, and many more.
The most noticeable information is the element kind. Array optimization is done in a very fine way. The array is divided into two types of elements:
Continuous Elements
Holey Elements: Arrays that contain holes.
Different languages have different types of array optimizations, but in JavaScript, optimization is divided into three types:
SMI (Small Integer)
Double (string, float, function)
Packed Elements
So, there are two types of arrays in JavaScript, and each has three types of optimizations.
Let me explain each kind with examples:
Continuous Elements
Arrays where all elements are contiguous and defined. There are no gaps or missing elements.
Types with examples:
Arr = [1, 2, 3]- This is called SMI_PACKED ELEMENT. It contains only proper numbers (integers).
Arr.push(3.5)Updated array:
Arr = [1, 2, 3, 3.5]This type of array is called a DOUBLE ELEMENT. When we push any decimal value to our array, it changes to a Double Element.
Arr.push('7')Updated array:
Arr = [1, 2, 3, 3.5, '7']This type of array is called a PACKED ELEMENT. When anything other than an integer or decimal number is added to our array, it changes to a Packed Element, which is the least optimized among the three types.
The order of optimization for Continuous Elements is:
- SMI_ELEMENT > DOUBLE > PACKED
Holey Elements
Holey elements are those that contain holes in the array.
Examples:
const ArrTwo = [1, , 2]- This is called SMI_HOLEY_ELEMENT (or H SMI): A holey array that contains simple integers but has a hole in it.
ArrTwo.push(6.0)Updated array:
ArrTwo = [1, , 2, 6.0]This is called a HOLEY_DOUBLE_ELEMENT (or H DOUBLE): A holey array that contains a decimal or float value.
ArrTwo.push('8')Updated array:
ArrTwo = [1, , 2, 6.0, '8']This is called a HOLEY_PACKED_ELEMENT (or H ELEMENT): A holey array that contains a string.
The order of optimization for array elements is:
SMI PACKED > DOUBLE PACKED > PACKED > HOLEY SMI > HOLEY DOUBLE > HOLEY PACKED
IMPORTANT NOTE: Once the optimization downgrades, it is impossible to upgrade it again. If you think removing the hole from an array makes it continuous or deleting string and float values from the array upgrades it, that is not possible because array optimization is done in such a way that once it downgrades, it will never upgrade. So, declare the array carefully.
The optimization of arrays is important because the more optimized the array, the lower its cost will be. I'll explain this with an example:
const ArrThree = [1, 2, 3, 4, , 5];
Applying bound check:
hasOwnProperty(ArrThree, 2): It will check array index 2 and return3as the output.hasOwnProperty(ArrThree, 9): It will check the array, and as it doesn't contain index9, it will returnundefined.
In these conditions, it only takes one step to check the values in the array. But what will happen if we check:
hasOwnProperty(ArrThree, 4): It will check the array, and since the 4th index in the array is empty, it will applyhasOwnProperty()one more time. This time, it will check its prototype.hasOwnProperty(ArrThree.prototype, 4): If it still doesn't find the value, it will now check the object, as the value can also be present in it.hasOwnProperty(ArrThree.Object, 4)
So, it takes three times more steps to check the value, and this makes the hasOwnProperty() method very expensive. It is the most expensive method in JavaScript, and calling it three times will raise its cost very high. So, it is preferred to make simple or most optimized arrays.
However, we can't always prioritize optimization over use cases. Use case scenarios have more priority than optimization, but we should take important steps to optimize where possible.
For example:
const arrFour = new Array(3); // H_SMI_ELEMENT
arrFour[0] = '1'; // H_ELEMENT
arrFour[1] = '2'; // H_ELEMENT
arrFour[2] = '3'; // H_ELEMENT
const arrFive = []; // SMI_PACKED
arrFive.push('1'); // PACKED_ELEMENT
arrFive.push('2'); // PACKED_ELEMENT
arrFive.push('3'); // PACKED_ELEMENT
In this example, we can't make it SMI_PACKED, but we can make it PACKED from H_PACKED.
EXTRA NOTE: Pushing Infinity or NaN into an array makes it a DOUBLE PACKED array.
For more imformation you all can check this video :