Node.js Internals- How to Write Faster & Better Optimized JavaScript Code

Written by Ryan Dahl in 2009, Node.js is a popular JavaScript runtime environment for executing server-side JavaScript code. Initially supporting only Linux and Mac OS, it can now run applications also on Microsoft Windows, NonStop and Unix servers.

In 2010 , a package manager by the name of npm was introduced for Node.js environment for the purpose of simplifying publishing & sharing of source code of Node.js libraries while later in June 2011, support for Windows was also introduced.

Basically, Node.js is a combination of Google’s V8 JavaScript engine, an event loop, and a low-level I/O API. Due to the extensive components of Node.js, coverage of the write-up is divided in two separate articles, each of which is centered on one of the components.

In this article, we are only going to cover the core of Node.js i.e. Google’s V8 engine.

Why Understanding of Node.js Internals Are Necessary ?

Having basic knowledge about the internal working of something is always beneficial. Similar is the case with JavaScript and Node.js. You may write JavaScript code without adequate knowledge of Node.js internals but the code may not be as efficient in terms of fast execution & better memory management.

This informative write-up will help you react differently while writing JavaScript code.

Node.js Architecture

Below diagram denotes a simplified version of Node.js architecture.

Following are the 3 main parts

  1. V8 Engine
  2. Node.js Bindings (Node API)
  3. An event loop

As discussed earlier, this article is only about V8 engine, more of it we’ll be covering in the next episodes of this series.

JavaScript Engine

JavaScript engine is a program or an interpreter which executes JavaScript code.

Below mentioned are some of the various JavaScript engines :-

  • Rhino from Mozilla
  • JavaScriptCode developed by Apple for Safari
  • JerryScript a lightweight engine for Internet of things
  • Chakra (JScript9) for Internet Explorer
  • Chakra (JavaScript) for Microsoft Edge
  • V8 from Google and so on.

Since Node.js uses Google V8, coverage of other JavaScript engines are beyond the scope of this article.

So without further ado, let’s start looking into Google V8.

Google V8

V8 is Google’s open source JavaScript engine, written in C++. It is not only used in Google Chrome but is also the part of popular Node.js. V8 directly translates JavaScript code into efficient machine code using JIT (Just-In-Time) compiler instead of using an interpreter.
Inside, the V8 engine, consists of several threads.

  • A thread for fetching JS code, compiling it & then executing it.
  • A separate thread for compiling, so that the main thread can keep executing while the former is optimizing the code .
  • A Profiler thread which tells the runtime on which methods we spend more time so that Crankshaft can try to optimize them.
  • Garbage Collection threads

Full-Codegen

At start , V8 utilizes Full-Codegen compiler which directly transforms parsed JavaScript code into machine code without any intermediate bytecode, this is why V8 does not needs an interpreter.This allows to start execution of the code as soon as possible.

Crankshaft

Now another thread starts using another compiler “Crankshaft” . Its main role is optimization .

In-lining

Inlining replaces the line of code where the function is called with the body of the called function for faster execution. C++ developers are used to with this concept.

Hidden Classes

It’s difficult for compiler to optimize dynamically typed language such as JavaScript because types of objects can be changed at runtime. At runtime V8 creates hidden classes that are attached objects to track their layout. So it is very important to maintain the shape of your objects so that V8 is able to optimize code efficiently.

Inline Caching

Inline caching relies on the observation that repeated calls to the same method tend to occur on the same type of object.
V8 uses a cache for the type of objects that were passed as a parameter in recent method calls and uses this knowledge to make an assumption about the type of object that will be passed as a parameter in the future. If this assumption is right, it can skip the process of figuring out how to access the object’s properties and can use the stored information from cache .

Garbage collection

Garbage collection is freeing up memory which is not in use anymore. Languages such as C have APIs for this like free() but JavaScript lacks such API. So this memory management task is handled by V8.

Newest Improvements in 2017, Ignition & TurboFan

Since V8 version 5.9 , (from Node.js version 8.3.0 onwards) , V8 uses a new execution pipeline to achieve significantly better performance and memory optimization. Ignition is V8 interpreter and Turbofan is a new optimizing compiler.
After inclusion of ignition and Turbofan , Full-codegen and Crankshaft are no longer in use by V8. Below graph shows significant speed improvement between V8 version 5.8 and 5.9.

Now, let us come to most important topic i.e. make use of above concepts to write better & optimized JavaScript code.

How to Write Faster & Better Memory Optimized JavaScript Code ?

Based on internal working of V8 , Alexander Zlatkov Co-founder & CEO SessionStack, recommends a list of guidelines to achieve faster and better memory optimized JavaScript code.

Here is a summary of his guidelines.

  1. Try to instantiate your object properties in the same order so that hidden classes, and subsequently optimized code, can be shared.
  2. Assign all properties of an object in its constructor to avoid hidden class change and hence to get better optimization.
  3. Due to inline caching, code that executes the same method repeatedly will run faster than code that executes many different methods only once.
  4. Avoid sparse arrays where keys are not incremental numbers. Elements in such arrays are more expensive to access. Also, avoid pre-allocating large arrays instead grow as you go. Finally, do not delete elements in arrays. It causes the keys to sparse.
  5. V8 represents objects and numbers with 32 bits. It uses a bit to find out if it is an object (flag = 1) or an integer (flag = 0) called SMI (SMall Integer) because of its 31 bits. If a numeric value is bigger than 31 bits, V8 will box the number, turning it into a double .

For example
var i = 52; // this is a 31-bit signed integer

var j = 5.2; // this is a double-precision floating point number
Hence, prefer numeric values that can be represented as 31-bit signed integers to avoid the expensive boxing operation into a JS object.

In next article of this series, we will cover the next major part of Node.js i.e. Event Loop along with best practices for writing JavaScript code based on internal working of Event Loop.

References

  1. https://blog.sessionstack.com/how-javascript-works-inside-the-v8-engine-5-tips-on-how-to-write-optimized-code-ac089e62b12e
  2. nodejs.org
  3. wikipedia.org/wiki/Node.js