📜 ⬆️ ⬇️

What is the secret of speed NodeJS?

We offer you a translation of the article by Evgeny Obrezkov , in which he briefly and deliberately talks about the reasons for the high speed of NodeJS: streams, event loop, optimizing compiler and, of course, a comparison with PHP. What could be without him.



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 needed in order to fulfill the request and send it back to the client. NodeJS is distinguished by high speed - starting from opening a connection and ending with sending a response.

Once you understand what is happening in the NodeJS server during the execution of the request, it will become clear to you why this happens so quickly.

But first, let's turn to how requests are processed in other languages. PHP is the best example because it is very popular and does not offer any optimizations by default.

What PHP suffers from


Here is a list of what reduces the speed of code execution in PHP:


These are the most critical cons of PHP. But, in my opinion, there are many more.

Now we will look at how NodeJS copes with similar 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, query the database, and perform other actions ... at the same time.

Each request does not create a separate NodeJS process. On the contrary, in NodeJS only one process is constantly 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.

The virtual machine in NodeJS (V8) that executes javascript has a JIT compilation. When the virtual machine gets the source code, it can compile it right while running. This means that operations that are often called can be compiled into machine code. And it will greatly improve the speed of execution.

In essence, the advantages of the asynchronous model were outlined here. Let me explain how this works in NodeJS.

Understand your asynchrony


I offer you an example of the concept of asynchronous processing (thanks to Kirill Yakovenko ).

Imagine that 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 can not push at the same time a thousand balls, only each separately. But this does not mean that you have to wait for the ball to reach the ground 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 is like having 1000 extra hands. And you can run all the balls at the same time. Then you wait only for the messages 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 big project, where there are a lot of requests, aggregations, and so on. When you process all data in a synchronous way, it blocks the execution of the code. Asynchronously, you execute all requests at the same time, and then only collect data.

In real life, when you have a lot of connections, it 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 a program. The event loop almost always works asynchronously with respect to the message source. When you invoke an I / O operation, NodeJS retains the callback associated with this operation and continues processing other events. Callback will be called when all necessary data is received.

The most extensive definition of the event loop:

An event loop, message dispatcher, message loop, message pump, or run loop is a software construct that waits for and processes events or messages in a program. The construct works by creating requests to an internal or external message delivery service (which usually blocks the request until the message is received), after which it calls the appropriate event handler (“handles the event”). An event loop can be used in conjunction with reactor if the event source has the same interface as the files to which you can make a request of the type select or poll (poll in the sense of the Unix system call). The 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 an operation in the thread pool with the required callback. The callback will be called when the request is completed. Your callback can also do other “heavy” operations, such as queries to the database. But it does it in the same way - it registers the operation in the thread pool with the necessary callback.

How about code execution and its speed? We are going to talk about the virtual machine and how it executes javascript code. That is about the V8.

How does V8 optimize your code?


Wingolog describes how the V8 virtual machine works. I simplified the material stated there and I suggest squeezing.

Below are 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 the compilers work. And if you want to know more about the V8, I advise you to contact a specialized source .

V8 has three types of compiler, but we will discuss only two: Full and Crankshaft (the third compiler is called Turbofun).

A full-compiler is fast and produces “sample code”. From the Javascript function, it takes AST ( Abstract Syntax Tree ) and translates it into 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 has determined which functions are used frequently and received a report on the use of types, she tries to run a modified AST through the optimizing compiler, Crankshaft.

Unlike the Full compiler, Crunshaft does not work so fast, but tries to produce optimized code. Cranshaft consists of two components: Hydrogen and Lithium.

The Hydrogen compiler generates a CFG ( Control Flow Graph ) from AST (based on the type usage report). This graph is presented in the form of SSA ( Static Single Assignment ). Based on a simple HIR structure ( High-Level Intermediate Representation ) and an SSA form, the compiler can apply many optimizations, such as constant folding, method inlining, and so on ...

The lithium compiler translates an optimized HIR to LIR (Low-Level Intermediate Representation). LIR is conceptually similar to machine code, but in most cases does not depend on the platform. 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 and continue to run your application much faster.

useful links


Source: https://habr.com/ru/post/303780/


All Articles