Node-addon-api: C++ addon is slower than js

Created on 11 Aug 2020  路  6Comments  路  Source: nodejs/node-addon-api

Hi,

We are considering rewriting some of our nodejs modules into native addons in C++, so I'm creating a small POC to show the performance difference. Unfortunately with the code I've written so far, c++ addon is actually slower than js code. This seems very strange so I wanted to share it here, I must be doing something wrong...

Napi::Value MyAddon::myFunc(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();
    if (!info[0].IsArray()) {
        Napi::TypeError::New(env, "You need to provide a valid array")
                .ThrowAsJavaScriptException();
        return env.Null();
    }
    Napi::Array arr = info[0].As<Napi::Array>();
    for (int i = 0; i < arr.Length(); i++) {
        if (arr.Get(i).ToBoolean() == false) {
            arr.Delete(i);
        }
    }

    return arr;
}

I'm sending an array of values to c++ addon and removing all values which are false from it. I have the same implementation in js.
Results from running both:

JsFunc*1000: 131.239ms
CppFunc*1000: 4753.354ms
JsFunc*1000: 123.122ms
CppFunc*1000: 4634.449ms

As you can see CppFunc is much slower that vanilla js implementation. I suspect that the slow performance comes from casting to napi array: Napi::Array arr = info[0].As<Napi::Array>();, because if I remove this and construct the array in c++, then it's faster.

Any ideas?

Most helpful comment

Hi @D-Andreev,
if you want to have an idea about the gain on performance that you could achieve using native add-ons you can test the following modules:

| Pure JavaScript | Native add-on |
|---|---|
| bcryptjs | bcrypt |
| jimp | sharp |
| crc64-ecma182.js | crc64-ecma182 |

All 6 comments

Hi @D-Andreev,
yes if you use native add-ons in place of plain JavaScript you will get the slower performance. The idea behind the native add-on is to give the possibility to bind C / C++ code and access to it directly from JavaScript. This will give you two benefits:

  • You can avoid to rewrite your C / C++ code
  • You can improve performance specially for CPU bound operations (think about at image processing).

In your case you could achieve at better performance using Napi::ArrayBuffer or Napi::TypedArray like reported in this part of the documentation https://github.com/nodejs/node-addon-api/blob/master/doc/basic_types.md#array.

If you have time maybe could red this beautiful article: https://medium.com/the-node-js-collection/speed-up-your-node-js-app-with-native-addons-5e76a06f4a40

if you want to know something more specific just ask.

Thanks for the info @NickNaso,

I'm not completely sure but I think the example I provided above is just too simple and v8 has some optimisations in place when executing the js code, that's why it was performing better than c++ addon. When I run my example for getting nth prime number:

Napi::Value MyAddon::isPrime(const Napi::CallbackInfo& info) {
    Napi::Env env = info.Env();

    if (!info[0].IsNumber()) {
        Napi::TypeError::New(env, "You need to provide a valid number")
                .ThrowAsJavaScriptException();
        return env.Null();
    }

    int n = info[0].ToNumber().Int64Value();

    bool isPrime = true;
    for (int i = 2; i <= n / 2; ++i) {
        if (n % i == 0) {
            isPrime = false;
            break;
        }
    }

    return Napi::Boolean::New(env, isPrime);
}

the performance for c++ addon is indeed better than js:

JsIsPrime*10000: 1191.665ms
CppIsPrime*10000: 849.511ms
JsIsPrime*10000: 1172.146ms
CppIsPrime*10000: 888.883ms

Hi @D-Andreev,
yes you are right and in general when you pass JavaScript value to the add-on those values are copied and there is a cost in terms of execution time for that.
In your first example your native code spent most of the time to copy the JavaScript values instead in the last one most of the time is spent to check for the primality of a number.

Hi @D-Andreev,
if you want to have an idea about the gain on performance that you could achieve using native add-ons you can test the following modules:

| Pure JavaScript | Native add-on |
|---|---|
| bcryptjs | bcrypt |
| jimp | sharp |
| crc64-ecma182.js | crc64-ecma182 |

I agree with @NickNaso. Communicating between the JS code and the native code is expensive. Therefore native addons only provide an advantage over pure JS if significant work is being done on the native side for each transition between JS code and native code.

Hi @D-Andreev,
I'm closing the issue, but feel free to reopen in case you need other information.

Was this page helpful?
0 / 5 - 0 ratings