This article is based on Nodejs V13.1.0

Before reading this article, read the precursors:

  • How did NodeJS work with Libuv and V8? (There are Easter eggs at the end of the article)

After reading this article, I hope you know the following:

  • C++ Addon introduced the principle
  • The difference between NAN and NAPI writing
  • Influence of C++ Addon on algorithm efficiency

This article demo address: portal

A brief history of NAN and NAPI

As mentioned in the history of NAN and NAPI in the article from violence to NAN to NAPI — the evolution of node.js native module development, NAN, in order to solve the “feudal” chaos of C++ native modules, instead of allowing a module to be used only by several nodejs versions, proposed the use of macro definitions to solve this problem. So NAN is a bunch of macro definitions that are compatible with all nodeJS versions. Do it once, compile everywhere.

The drawback of this design model is that multiple compilations, which means that if you write a plug-in to a higher Nodejs version, you still need to compile it again, allowing the macro definition to match the new version to compile a new plug-in package. After nodev8, Nodejs introduced a new mechanism. And that’s what we’re going to be talking about — NAPI.

Different versions of Node.js use the same Interface, which is stably ABi-abi, i.e., the Application Binary Interface. This allows compiled C++ extensions to be used directly across node.js without recompiling as long as the ABI version number is the same.Copy the code

So how do we check the ABI version of the current Node version? The current ABI version can be printed with process.versions.modules, and Nodejs provides a complete list of ABI versions:

abi_version_registry.json

Process. versions provides hints about Nodejs versions, including the version used by V8 and the versions of each dependent package, such as printed under version V13.2.0: (Note: the nAPI field is the nAPI module version, which has its own version matrix:N-API Version Matrix)

{
  node: '13.2.0',
  v8: '20' 7.9.317.23 - node.,
  uv: '1.33.1',
  zlib: '1.2.11',
  brotli: '1.0.7',
  ares: '1.15.0',
  modules: '79',
  nghttp2: '1.40.0',
  napi: '5',
  llhttp: '1.1.4',
  openssl: 1.1.1 'd',
  cldr: '35.1',
  icu: '64.2',
  tz: '2019c',
  unicode: '12.1'
}
Copy the code

N-api defines the following features:

  • Provide header filesnode_api.h;
  • Any N-API call returns onenapi_statusEnumeration to indicate whether the call was successful or not;
  • The return value of n-api due to beingnapi_statusTrap, so the true return value is passed by the parameter;
  • allJavaScriptData types are black box typesnapi_valueEncapsulation is no longer analogous tov8::Object,v8::NumberSuch as the type;
  • If the function call is unsuccessful, passnapi_get_last_error_infoFunction to get information about the last error.

1.1. Since n-API is ABI based, why is it notmodulesFields are instead custom madenapiThis field?

This question is left for you to think about. If you have any questions, please leave a comment.

How does Nodejs introduce the implementation of a C++ plug-in?

Old rules, as shown below:

Why do JSON files automatically convert to objects when they are required directly?

Because when the module parses, if the suffix is JSON, it will callJSON.parseThis method

The picture above can also be used as a simple answer to the interview question “Please describe a simple process after requiring a file in Nodejs”

We focused on the DLOpen method in V8.

The node module is a dynamically linked library (DLL for Windows, so for Linux), so you can use APIdlopen() to open the __POSIX__ standard. If it is not __POSIX__, it can only be opened with libuv’s uv_dlopen method.

Second, after the file is opened, execute the following judgments:

  • Determines whether the module’s initialization function meets the standard
  • Determine if it is a normal C++ plug-in, and if so, see if the current nodejs version ABI version supports loading the module

Finally, the module’s initialization function is executed

N-api modules do not need to be judged.

A given version n of N-API will be available in the major version of Node.js in which it was published, and in all subsequent versions of Node.js, including subsequent major versions.
Copy the code

2.1. Build and execute NAN modules with different versions

If nodeJS is compiled using node V11.15.0 and then executed using node V13.2.0, nodeJS will report the following error:

:16
internal/modules/cjs/loader.js:1190
  return process.dlopen(module, path.toNamespacedPath(filename));
                 ^

Error: The module '/Users/linxiaowu/Github/nodejs-NAPI-demo/packages/md5-NAN/build/Release/md5-nan.node'
was compiled against a different Node.js version using
NODE_MODULE_VERSION 67. This version of Node.js requires
NODE_MODULE_VERSION 79. Please try re-compiling or re-installing
the module (forinstance, using `npm rebuild` or `npm install`). at Object.Module._extensions.. node (internal/modules/cjs/loader.js:1190:18) at Module.load (internal/modules/cjs/loader.js:976:32) at Function.Module._load (internal/modules/cjs/loader.js:884:14) at Module.require (internal/modules/cjs/loader.js:1016:19)  at require (internal/modules/cjs/helpers.js:69:18) at Object.<anonymous> (/Users/linxiaowu/Github/nodejs-NAPI-demo/packages/md5-NAN/index.js:1:15) at Module._compile (internal/modules/cjs/loader.js:1121:30) at Object.Module._extensions.. js (internal/modules/cjs/loader.js:1160:10) at Module.load (internal/modules/cjs/loader.js:976:32) at Function.Module._load (internal/modules/cjs/loader.js:884:14)Copy the code

2.2. Compile and execute n-API modules with different versions

It is possible to execute the NAPI module normally using the same steps described above. Process. versions of V11.15.0:

{ node: '11.15.0',
  v8: 19 ' '7.0.276.38 - node.,
  uv: '1.27.0',
  zlib: '1.2.11',
  brotli: '1.0.7',
  ares: '1.15.0',
  modules: '67',
  nghttp2: '1.37.0',
  napi: '4',
  llhttp: '1.1.1',
  http_parser: '2.8.0',
  openssl: 1.1.1 'b',
  cldr: '34.0',
  icu: '63.1',
  tz: '2018e',
  unicode: '11.0' }
Copy the code

For V13.2.0, process.versions are:

{
  node: '13.2.0',
  v8: '20' 7.9.317.23 - node.,
  uv: '1.33.1',
  zlib: '1.2.11',
  brotli: '1.0.7',
  ares: '1.15.0',
  modules: '79',
  nghttp2: '1.40.0',
  napi: '5',
  llhttp: '1.1.4',
  openssl: 1.1.1 'd',
  cldr: '35.1',
  icu: '64.2',
  tz: '2019c',
  unicode: '12.1'
}
Copy the code

This verifies that n-API does a good job of compatibility and compilation.

3, NAN and NAPI writing comparison

Using NAN and N-API as examples in the demo, an MD5 is implemented respectively. The following is a comparison of the two:

Let’s go through it line by line.

NAN uses the third-party package NAN, and N-API uses the third-party package Node-addon-api

3.1. Head ①

As you can see from the header, n-API’s header files are cleaner, without those V8 statements. Because you don’t need to use it like NAN:

using v8::Local;
using v8::Object;
using v8::String
Copy the code

3.2 implementation part ②

In the implementation part, NAN uses macro definitions to wrap the header of the implemented function, whereas n-API, wrapped in Node-Addon-API, is more like a normal function, with function names, parameters, and return values. Function body implementations are not very different, except for the return value:

NAN:

info.GetReturnValue().Set(New(md5str).ToLocalChecked());

The v8 very much!

And N – API:

return String::New(env, md5str,32);

Very normal!

3.3. Initialize function ③

The difference between the two is obvious:

NAN:

Nan::HandleScope scope;
Nan::SetMethod(exports, "md5", md5);
Copy the code

N – API:

exports.Set("md5", Function::New(env, GetMD5));
return exports;
Copy the code

3.4. Module definition ④

NAN module initialization is handed to the macros provided with Node.js:

NODE_MODULE(addon, init)

N-api uses its own macro definition (NAPI_MODULE), and since we use Node-Addon-API, it wraps this macro definition as follows:

NODE_API_MODULE(addon, Init)

4. Can NAPI improve algorithm efficiency?

Let’s take a look at an efficiency boost to the sorting algorithm using N-API. In the example, two sorting algorithms are used: bubble sort and quicksort:

Code reference: sort.cc

Take quicksort as an example. The time complexity of quicksort algorithm is NlogN, C version of quicksort:

void quickSort(unsigned int *array, unsigned int length)
{
  unsigned int partition;
  unsigned int i, j;
  unsigned int rightLength, leftLength;
  unsigned int *rightArray, *leftArray;

  if (length < 2)
  {
    return;
  }
  partition = *(array);
  i = 1;

  for (j = 1; j <= length; j++)
  {
    if (*(array + j) < partition)
    {
      swap(array + i, array + j);
      i++;
    }
  }
  swap(array, array + i - 1);

  leftLength = i - 1;
  leftArray = array;
  rightArray = array + i;
  rightLength = length - i;

  quickSort(rightArray, rightLength);
  quickSort(leftArray, leftLength);
}
Copy the code

When we change the length of the array in test/index.js, we get the js version sort time and n-API sort time (in ms) as shown below:

As can be seen from the figure, with the increase of the array length, the sort time of JS version is even better than that of N-API, which can be said to be equal. However, in the bubble sort, the sort time of N-API is always better than that of JS version. Two conclusions can be drawn:

  • The importance of selection algorithms!
  • V8 has done a great job optimizing the code!

reference

  1. From violence to NAN to NAPI — The evolution of node.js native module development
  2. ABI Stability
  3. Shared library handling
  4. process.versions.modules is not for N-API ?