Why NodeJS is so fast?

Eugene Obrezkov
Eugene Obrezkov
Published in
6 min readNov 14, 2015

Here I am again with an article about NodeJS! Today I want to speak about another NodeJS advantage — execution speed.

What do I mean by “execution speed”?

It can be anything ranging from calculating Fibonacci sequence to querying database.

When talking about web-services, execution speed consists of everything that is needed to process request and send the response back to the client. That’s what I mean — time spent on processing request, starting from opening connection to client receiving the response.

As soon as you understand what’s going on in NodeJS server when it processes the requests, you will realise why it is so fast.

But before talking about NodeJS, let’s look at how request handling is done in other languages. PHP is the best example because it’s very popular and unoptimised.

PHP drawbacks

Here is a list of drawbacks that decrease execution performance in PHP:

  • PHP is synchronous. It means when you are processing a request, writing to the database, for instance, it blocks any other operations, so you will have to wait for it to finish, before you can do anything else.
  • Each request to web-service creates a separate PHP interpreter process which is running your code. Thousands of connections means thousands of running processes that take your RAM. You can see how your used memory is growing up with your active connections.
  • PHP doesn’t have JIT compilation. It’s important when you have code that is executed very often and you want to be sure that this code is as close to machine code as possible for better performance.

These are only the most critical problems with PHP, but there are a lot more, in my humble opinion.

Now let’s see how NodeJS handles these problems.

NodeJS advantages

  • NodeJS is single-threaded and asynchronous. Every I/O operation doesn’t block other operations. It means that you can read files, send mails, query the database, etc… at the same time.
  • Each request to web-server does NOT create a separate NodeJS process. Instead, one NodeJS process is running at all times and listens to connections. JavaScript code is executed in the main thread of this process and all I/O operations are executed in separate threads, meaning almost no delays.
  • Virtual machine in NodeJS (V8) that executes JavaScript has JIT compilation. When virtual machine takes source code, it can compile it to machine code at runtime. It means that “hot” functions, that get called very often, can be compiled to machine code, improving execution speed significantly.

Now that we’ve seen advantages of asynchronous concept, let me explain how it works in NodeJS.

Know your asynchronous!

Let me give you an example of asynchronous processing concept (thanks to Kirill Yakovenko).

Imagine you had 1,000 balls on the top of the mountain. You need to push them all to the bottom of the mountain. You can’t really push them all at the same time, you’ll have to push them one by one, but that doesn’t mean that you have to wait for each ball to get to the bottom of the mountain before pushing the next one.

In this example, synchronous execution would be waiting for a ball to roll down to the bottom before pushing another one, which is a waste of time.

Asynchronous execution, on the other hand, would be pushing all the balls one by one immediately, then waiting for them all to get down (receiving a notification) and collecting results.

How does this help in web-server performance?

Lets say that each ball is one query to the database. You have a big project with many queries, aggregations, etc… When you are processing all the data in synchronous manner it blocks the code execution. When you are processing it in asynchronous manner, you can execute all the queries at once and then just collect the results.

In real world, when you have a lot of connections, it improves performance significantly.

How is asynchronous concept implemented in NodeJS?

Event loop

Event loop is a construction that is responsible for dispatching events in a program that almost always operates asynchronously with the message originator. When you call an I/O operation, NodeJS stores the callback assigned with that operation and continue processing other events. Callback will be triggered when all needed data is collected.

Here is more advanced definition of event loop:

The event loop, message dispatcher, message loop, message pump, or run loop is a programming construct that waits for and dispatches events or messages in a program. It works by making a request to some internal or external “event provider” (which generally blocks the request until an event has arrived), and then it calls the relevant event handler(“dispatches the event”). The event-loop may be used in conjunction with a reactor, if the event provider follows the file interface, which can be selected or ‘polled’ (the Unix system call, not actual polling). The event loop almost always operates asynchronously with the message originator.

Let’s look at a simple graph that explains how event loop works in NodeJS.

NodeJS Event Loop

When a request is received by web-server it goes to the event loop. Event loop registers operation in a thread pool with assigned callback. Callback will be triggered when processing request is done. Your callback also can do other intensive operations like querying the database, but it does so the same way — registers operation in a thread pool with assigned callback and so on…

But what about code execution and its speed? Next, we are going to talk about virtual machine that executes JavaScript code — V8.

How V8 optimises your code?

Thanks to Wingolog, where it is described how V8 works, I can simplify that material and make thesis.

I’m going to talk about basic concepts of V8 and how it optimises JavaScript code, but it will still be highly technical, so fell free to skip this chapter if you are not familiar with how compilers work. If you want to know more about V8, go to V8 Resources.

V8 has two types of compilers (There is also a third compiler in development which is called Turbofan): “Full” compiler and “Crankshaft” compiler.

“Full” compiler runs fast and produces generic code. It takes AST (Abstract Syntax Tree) of a JavaScript function and translates it to generic native code. At this stage only one optimisation is applied — Inline Caching.

When function was compiled and code is running, V8 starts the profiler thread to see, which functions are hot and which are not, also collecting the type feedback, so V8 can record the type information flowing through it.

Once V8 has identified that a function is “hot” and it has some type feedback information, it tries to run the augmented AST through an optimising compiler — “Crankshaft” compiler.

“Crankshaft” compiler doesn’t run as fast but does try to produce the optimised code. It consists of two components: Hydrogen and Lithium.

Hydrogen compiler builds CFG (Control Flow Graph) from AST (based on type feedback information). This graph is presented in SSA (Static Single Assignment) form. Based on simple structure of HIR (High-Level Intermediate Representation) and SSA form, compiler can apply many optimisations like constant folding, method inlining, etc…

Lithium compiler translates the optimised HIR into a LIR (Low-Level Intermediate Representation). The LIR is conceptually similar to machine code, but still mostly platform-independent. In contrast to HIR, LIR form is closer to three-address code.

Only then, this optimised code can replace the old unoptimised code and continue executing your code much faster.

Useful links

Thanks for reading!

Eugene Obrezkov, Technical Leader and Consultant at Onix-Systems, Kirovohrad, Ukraine.

--

--

Eugene Obrezkov
Eugene Obrezkov

Software Engineer · elastic.io · JavaScript · DevOps · Developer Tools · SDKs · Compilers · Operating Systems