What is the secret of NodeJS speed?
- Transfer
We offer you a translation of an article by Evgeny Obrezkov , in which he briefly and briefly talks about the reasons for the high speed of NodeJS: threads, an event loop, an optimizing compiler and, of course, a comparison with PHP. Where without it. In another article about NodeJS, I want to talk about another advantage of the software platform: the speed of code execution.
What do we mean by execution speed
Calculate the Fibonacci sequence or send a query to the database?
When we talk about web services, the execution speed includes all the actions that are necessary in order to fulfill the request and send it back to the client. NodeJS is distinguished by its high speed - from opening a connection to sending a response.
Once you understand what is going on in the NodeJS server during the execution of the request, it will become clear to you why this is happening so quickly.
But first, let's look at how requests are processed in other languages. PHP is the best example because it is very popular and does not offer any default optimizations.
What PHP suffers from
Here is a list of what reduces code execution speed in PHP:
- PHP has a synchronous execution model. This means that when you process the request or write to the database, other operations are blocked. Therefore, you have to wait for the end of the operation before you start to do something else.
- Each request to the web service creates a separate PHP interpreter process that executes your code. Thousands of connections mean thousands of running processes that consume memory. You can observe how memory is being used more and more along with new connections.
- PHP does not have a JIT compiler. This is important if you have code that is used very often, and you want to be sure that it is as close to machine code as possible.
These are the most critical cons of PHP. But, in my opinion, there are many more.
Now we will see how NodeJS deals with such tasks.
Magic NodeJS
NodeJS is single-threaded and asynchronous. Any I / O operation does not block the operation. This means that you can read files, send emails, request a database and perform other actions ... at the same time.
Each request does not create a separate NodeJS process. In contrast, in NodeJS, there is only one process running and waiting for connections. JavaScript code is executed in the main thread of this process, and all I / O operations are performed in other threads with almost no delay.
A virtual machine in NodeJS (V8) that runs JavaScript has JIT compilation. When the virtual machine receives the source code, it can compile it right at runtime. This means that operations that are called frequently can be compiled into machine code. And it will greatly improve the execution speed.
Essentially, the advantages of an asynchronous model were outlined here. Let me explain how this works in NodeJS.
Understand your asynchrony
I bring to your attention an example of the concept of asynchronous processing (thanks to Kirill Yakovenko ).
Imagine you have 1000 balls on top of a mountain. And your task is to push all the balls so that they are at its base. You cannot push a thousand balls at the same time, only each individually. But this does not mean that you have to wait until the ball reaches the base in order to push the next one.
Synchronous execution means a waste of time for you. You are waiting for the ball to be at the base.
Asynchronous execution looks like you get 1000 extra hands. And you can run all the balls at once. Then you wait only for the message that they are all below, and collect the results.
How does asynchronous execution help a web service work?
Imagine that each ball is a query to the database. You have a large project where there are a lot of requests, aggregations, and so on. When you process all the data in a synchronous way, this blocks the execution of the code. Asynchronously, you execute all the requests at once, and then only collect the data.
In real life, when you have many connections, this greatly speeds up the work.
How is the asynchronous method implemented in NodeJS?
Event loop
An event loop is a construct that is responsible for handling events in some program. An event loop almost always works asynchronously with respect to the message source. When you invoke an I / O operation, NodeJS saves the callback associated with this operation and continues processing other events. A callback will be called when all the necessary data is received.
The most detailed definition of event loop:
An event loop, message dispatcher, message loop, message pump or run loop is a software construct that expects and processes events or messages in a program. The design works by creating requests to the internal or external message delivery service (which usually blocks the request until the message is received), after which it calls the handler of the corresponding event (“processes the event”). An event loop can be used in conjunction with a reactor if the event source has the same interface as the files to which you can make a request of the form select or poll (poll in the sense of Unix system call). An event loop almost always works asynchronously with respect to the message source.
Let's look at a simple illustration that explains how the event loop works in NodeJS.

Event Loop in NodeJS
When a web service receives a request, it is sent to the event loop. The event loop registers the operation in the thread pool with the desired callback. A callback will be called when request processing is complete. Your callback may also do other “heavy” operations, such as querying the database. But it does it in the same way - it registers the operation in the thread pool with the desired callback.
What about code execution and speed? We are going to talk about a virtual machine and how it executes JavaScript code. That is about V8.
How does V8 optimize your code?
In Wingolog described how the virtual machine V8. I simplified the material presented there and offer a squeeze.
Below we will outline the basic principles of the V8 virtual machine and how it optimizes JavaScript code. This will be technical information, so you can skip this part if you do not know how compilers work. And if you want to know more about V8, then I advise you to consult a specialized source .
V8 has three types of compiler, but we will discuss only two: Full and Crankshaft (the third compiler is called Turbofun).
Full-compiler is fast and produces “sample code”. From the Javascript function, it takes AST ( Abstract Syntax Tree) and translates it into a typical native code. At this stage, only one optimization is applied - inline caching .
When the code is compiled and run, V8 starts the profiler thread to find out which functions are used frequently and which are not. The virtual machine also collects type usage reports so that it can record the types of information that passes through it.
After V8 determined which functions are used frequently and received a report on the use of types, she tries to run the modified AST through the optimizing compiler - Crankshaft.
Unlike the Full compiler, Crunshaft is not so fast, but it tries to produce optimized code. Cranshaft consists of two components: Hydrogen and Lithium.
Hydrogen compiler creates CFG ( Control Flow Graph ) from AST (based on type usage report). This graph is presented in the form of SSA ( Static Single Assignment ). Based on the simple HIR ( High-Level Intermediate Representation ) structure and SSA form, the compiler can apply many optimizations, such as constant folding, method inlining, and so on ... The lithium compiler
translates the optimized HIR to LIR (Low-Level Intermediate Representation). LIR is conceptually similar to machine code, but in most cases is platform independent. In contrast to HIR, the LIR form is closer to the three-address code.
Only after that, the optimized code can replace the old non-optimized one and continue to run your application much faster.