Skip to main content

How to extend the Node.js application with C ++ addons

Node.js takes JavaScript beyond browser development to a variety of different platforms including:

  • Server-side platforms
  • Desktop applications with Electron, NW.js, Squoosh, and others
  • Microcontrollers, command-line tools for automation, builds, testing, and more

So what is the main peculiarity of Node.js development? It’s the network applications building. Node.js is a real star in fast and scalable network applications development. However, there are scenarios when Node.js is less effective than other technologies:

  • High-accuracy calculations

    JavaScript is not a high-accuracy calculation language, that’s why it’s common that some calculation inaccuracies (especially for the float numbers) can arise.

  • Parallel calculations

    Node.js shines in single-thread calculations for network applications. However, Node.js doesn’t have out-of-the box solutions for parallel processing. For this, you need to install additional packages.

  • Low level computations

    As JavaScript is a high level programming language, it doesn't have efficient tools for low level tasks, such as working with RAM or certain hardware.

Of course, such problems are solved with the help of packages or extensions. But in this article, I want to show how to extend the Node.js performance in these cases.

Let’s dive in.

Node.js and C++

Node technology was written on C++ under the hood which is very fast and powerful in development of applications for low level tasks or heavy computations.

As a result, Node is C++ friendly and allows developers to run C++ code in Node.js applications. This is done with the help of Node addons.

Node.js Addons are dynamically-linked shared objects, written in C++. They can be loaded into Node.js using the require() function, and used just as if they were an ordinary Node.js module. They are used primarily to provide an interface between JavaScript running in Node.js and C/C++ libraries.

What power do Node.js addons give to us?

  • Opportunity to make intensive, parallel and high-accuracy calculations
  • Opportunity to use C++ libraries That sounds interesting in theory, but what about practice?

In this tutorial, I will сomplete the same task first with JavaScript and then with C++ addons and show you the difference in performance.

Node JS addons performance testing

First of all, we will give a tribute to the traditions and develop“Hello world!” application with C++ addon .

The project setup will consist of several stages:

  • Pre setup

    To start the work, install the following software on your computer:

  • Node JS

  • Npm Npm is the package manager for Node. It loads and manages ready solutions such as libraries and modules. Usually, Node is installed together with Npm. Here you can find a tutorial on how to install Node with the command line for the most popular operating systems.

Step 1

To initialize a new Npm directory, execute the command code: npm init. You will have to answer several questions to set the project. Finally, the package.json file that contains the basic information about the project will be created in the project directory. The list of necessary packages will be also stored in this file.

execute
code: npm init
to setup new project.

Step 2

After you have set up the project, install the necessary packages. In this project you need only one package - “node-gyp”. It’s a tool that compiles Node.js addons.

Simply speaking, node-gyp compiles C/C++ for using it with Node.js. This package is compulsory for a lot of apps, as in many cases code that should be compiled is loaded along with the package.

One more peculiarity of node-gyp is that it compiles the code regardless of the operating system. This feature allows avoiding a number of mistakes related to the differences in operating systems for Node-based projects.

To set up node-gyp, execute the command npm install g node-gyp

Npm - project name
Install - action
Node-gyp - package to install
g- flag, that indicates that the project has to be installed globally and used for any Node project on this operating system.

Step 3

After you have installed the package for C/C++ code compilation, configure node-gyp within the project. First, create binding.gyp file inside the project. This file will contain information about node-gyp functioning.

Gyp configuration.
Create binding.gyp file
File content

{
   "targets": [
       {
           "target_name": "addon",
           "sources": [ "addon.cc" ]
       }
   ]
}

"Target_name" - with its help you can connect the compiled C++ code
“Sources” - array of *.cc file names that contain C++ code

Step 4

Write and compile addon code

Let's write the addon file. In the root of my project I create ‘addon.cpp’ file.

We start from including modules.

#include <node.h>
#include <iostream>

<node.h> is required for building some kind of interface with Node application.

C/C++ languages have no built-in input/output. Instead of it, let’s use the iostream library which is included into standard C++ and contains methods to manage input / output.

using namespace v8;
using namespace std;

With the help of this code we can connect the namespaces to our file. Variables, located in the namespaces listed above are now available in our file.

For example, we will use “cout” and “endl” functions from the “std” namespaces, and also “FunctionCallbackInfo”, “Value”, “Local”, “Object” from the V8 namespace. We don’t import these functions or variables. They appear in the file after namespaces connection.

void HelloWorld(const FunctionCallbackInfo<Value>& args) {
   cout << "Hello, world!" << endl;
}

This part of code is easy to understand. We just describe a function “HelloWorld” which gives homonym line to standard output thread. Also this function has two parameters: callback and args array, but we don’t need them yet.

void Initialize(Local<Object> exports) {
   NODE_SET_METHOD(exports, "helloWorld", HelloWorld);
}

This function is responsible for the HelloWorld function export.

It will be exported with the name “helloWorld”. NODE_SET_METHOD accepts 3 arguments:

  • the exports object,
  • name under which the function will be exported,
  • the function itself. As you see, you can export the function under any name.

Run compiled C++ code with using common Node Application.

The last step is to initialize the addon. Note that the first argument should have the same name as in configuration file from the third step. Here is a total code of addon file

#include <node.h>
#include <iostream>

using namespace v8;
using namespace std;

void HelloWorld(const FunctionCallbackInfo<Value>& args) {
   cout << "Hello, world!" << endl;
}

void Initialize(Local<Object> exports) {
   NODE_SET_METHOD(exports, "helloWorld", HelloWorld);
}

NODE_MODULE(addon, Initialize);

After that we can compile our addon with the help of node-gyp configure buildcommand. If you have run this command and everything works just fine - congratulations! You have avoided the compilation errors which is some kind of miracle for JS developers:)

Just a joke. If seriously, when everything’s right, the folder build will appear in the project directory. It will contain just compiled addon code.

Step 5

The last step is to create a general JS file to test the compiled addon in action.

Create “my_app.js” file in the root of project.

It can contain 3 lines of code.

const addon = require('./build/Release/addon');

const runAddon = () => addon.helloWorld();

runAddon();

First line - importing of our addon.

Second line (optional) - function wrapper to call our addon “helloWorld” exported function;

Third line - function call.

If you do everything correct you can run this file with command “node my_app”.

Here is an expected result:
This is image title
As you can see, your app puts Hello world message into console and this action was programmed in “addon.cc” file on C++ language.

Java Script vs C++ performance

We already know how to run C++ code in Node.js applications. Now it’s time to have some fun and use this skill in more engaging tasks.

Let’s make some heavy computations that require high accuracy both on JS and C++ and compare the execution time in numbers.

First, let’s expand the addon with one more function.

void AddNumbers(const FunctionCallbackInfo<Value>& args) {
   Isolate* isolate = args.GetIsolate();
   double valueToSum = args[0]->NumberValue();
   double result = 0;
   int sumCount = args[1]->IntegerValue();
   int i;

   for (i = 0; i < sumCount; i++) {
       result = result + valueToSum;
   }

   args.GetReturnValue().Set(result);
}


void Initialize(Local<Object> exports) {
   NODE_SET_METHOD(exports, "helloWorld", HelloWorld);
   NODE_SET_METHOD(exports, "addNumbers", AddNumbers);
}

With the help of args object we get two parameters that will be transmitted when calling the function.

double valueToSum = args[0]->NumberValue();- get the value we will add in the cycle and turn it into a real number.

int sumCount = args[1]->IntegerValue(); - second parameter is the number of times the number will be summed. We turn it into a real number as well.

We add the number in the cycle and save a result into the variable “result”.

A similar functionality was written in Js file. Let’s check its execution time and compare it to the previous one.

const addon = require('./build/Release/addon');

const addNumbersAddon = () => addon.addNumbers(3.14, 1000000);

const addNumbersNode = (num, count) => {
 let result = 0;
 for (let i = 0; i < count; i++) {
   result = result + num;
 }
 return result;
};

A function described below calls the developed logic in C++ and in JS as well, transmitting functions into the same parameters. So we will do the same task with the same input data but different tools.

const addNumbers = (number, addingTimes) => {
 console.time('C++');
 console.log(addNumbersAddon(number, addingTimes));
 console.timeEnd('C++');

 console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>');

 console.time('JS');
 console.log(addNumbersNode(number, addingTimes));
 console.timeEnd('JS');
};

addNumbers(3.14, 100000000);

For our performance test we will add 3.14 number for a hundred millions times.

Here are the results:
This is image title
C++ in this case works much much faster and gives us more accurate results.

Сonclusion

I have written this article not to show how bad JS is against the background of splendid C++. Each programming language was designed for certain applications it deals well with. The aim of this article is to show how you can expand your Node app with the help of C++ addons and get better productivity in custom computation-intensive applications.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...