Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

C++ addon is slower than js #790

Closed
D-Andreev opened this issue Aug 11, 2020 · 6 comments
Closed

C++ addon is slower than js #790

D-Andreev opened this issue Aug 11, 2020 · 6 comments

Comments

@D-Andreev
Copy link

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?

@NickNaso
Copy link
Member

NickNaso commented Aug 11, 2020

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.

@D-Andreev
Copy link
Author

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

@NickNaso
Copy link
Member

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.

@NickNaso
Copy link
Member

NickNaso commented Aug 12, 2020

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

@gabrielschulhof
Copy link
Contributor

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.

@NickNaso
Copy link
Member

NickNaso commented Sep 4, 2020

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants