How to Improve Node.js Productivity With C++ Addons: Step by Step Guide

How to Improve Node.js Productivity  With C++ Addons: Step by Step Guide

Node.js is an open-source platform for development of the applications with JavaScript programming language. Node.js is based on the V8 engine that executes JavaScript code. 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 mobile application 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.

 

Get the best tech solution for your project!

Let's discuss how the Apiko Team can assist you in developing a robust and intuitive software to meet your business objectives.
Michel Rokosh
 

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 build command. 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:

Node-performance-c++

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:

Node-js-performance

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.