Repository navigation
How to generate JsObject in Node.js c++ addon as fast as in pure js? #1074
Description
Activity
And I found the Nan c++ addon is faster than node-addon-api, but still slower than pure JS....
like this:
#include <nan.h> void CreateObject(const Nan::FunctionCallbackInfo<v8::Value>& info) { v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext(); v8::Local<v8::Object> node = Nan::New<v8::Object>(); v8::Local<v8::Array> body = Nan::New<v8::Array>(); for (auto i = 0; i < 2000000; i++) { v8::Local<v8::Object> el = Nan::New<v8::Object>(); el->Set(context, Nan::New("foo").ToLocalChecked(), Nan::New<v8::Number>(1)); Nan::Set(body, i, el); } node->Set(context, Nan::New("body").ToLocalChecked(), body); info.GetReturnValue().Set(node); } void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) { v8::Local<v8::Context> context = exports->CreationContext(); module->Set(context, Nan::New("exports").ToLocalChecked(), Nan::New<v8::FunctionTemplate>(CreateObject) ->GetFunction(context) .ToLocalChecked()); } NODE_MODULE(addon, Init)time cost:
perf: 1.559sHi @zhuyingda,
when you implement a native addon there is a cost on passing data as input and output to your native function. The data you pass will be copied from JavaScript side to native side and vice versa. Usually the time you spent on pass data is not relevant to the time taken to complete some computation. If you need to tranfer large amounts of data from JavaScript to native side the best solution shoul beNapi::TypedArrayandNapi::ArrayBufferandNapi::Bufferin this case the buffer is shared between the JavaScript and native side. This is documented here: https://github.com/nodejs/node-addon-api/blob/main/doc/array.md and you can find an exapmle here: https://github.com/nodejs/node-addon-examples/tree/HEAD/array_buffer_to_native/node-addon-api
Node-API is technically a layer over the Node.js and V8 APIs this is the reason because using NAN you have better performance. NAN is a thin and transparent layer over the Node.js and V8 APIs it guarantees the API stability, but you need to recompile your native addon changing the Node.js version, instead the Node-API should guarantees the API and ABI stability this means that you don't need to recompile you native module switching Node.js version.Reacted by Michael Dawson and isaac sakyiReacted by Sugar ZhuIn addition to what Nick has said. After doing some testing (not returning the object, void function) it looks like the addon still takes a while to finish. If your code requires this big for loop to work, then I recommend also trying out the
ThreadSafeFunctionto split up work among other threads. As for why pure js code executes faster I am not quite sure. My guess is that v8 does some optimization behind the scenes at the assembly level?Reacted by Sugar ZhuIn addition to what Nick has said. After doing some testing (not returning the object, void function) it looks like the addon still takes a while to finish. If your code requires this big for loop to work, then I recommend also trying out the
ThreadSafeFunctionto split up work among other threads. As for why pure js code executes faster I am not quite sure. My guess is that v8 does some optimization behind the scenes at the assembly level?I want to figure out how could I generate JSObject in c++ addon also benefit from the optimization which v8 does behind this scenes?
when you implement a native addon there is a cost on passing data as input and output to your native function. The data you pass will be copied from JavaScript side to native side and vice versa. Usually the time you spent on pass data is not relevant to the time taken to complete some computation.
Hi @NickNaso, thanks for your reply.
Actually, after doing a lot of testing, the data passing between JavaScript side and native side is not the main slowness (just like @JckXia said).
I change previous NAPI code to this:Napi::Array body = node.Get("body").As<Napi::Array>(); Napi::Object stmt = Napi::Object::New(m_env); stmt.Set(Napi::String::New(m_env, "foo"), Napi::Number::New(m_env, 1)); for (int i = 0; i < 2000000; i++) { body.Set(Napi::String::New(m_env, std::to_string(i)), stmt); }it just cost:
perf: 794.548msAnd also, in NAN case:
v8::Local<v8::Object> el = Nan::New<v8::Object>(); el->Set(context, Nan::New("foo").ToLocalChecked(), Nan::New<v8::Number>(1)); for (auto i = 0; i < 2000000; i++) { Nan::Set(body, i, el); }it just cost:
perf: 281.696msAKA the main time-cost is because of
Napi::Object::New()andstmt.Set().After moving the Object::New() out of the big for loop, it save a lot of time.
However, the code will not work anymore, because all element in the JS-Object-Array is the same JsObject...
So, I also found another way in NAN addon that I can useV8::ObjectTemplateto optimize JsObject generating in c++:v8::Local<v8::ObjectTemplate> objTpl = Nan::New<v8::ObjectTemplate>(); objTpl->Set(Nan::New("foo").ToLocalChecked(), Nan::New<v8::Number>(1)); for (auto i = 0; i < 2000000; i++) { Nan::Set(body, i, objTpl->NewInstance(context).ToLocalChecked()); }This time cost:
perf: 736.499msAlso, I change the NAN c++ addon code to this:
#include <nan.h> void CreateObject(const Nan::FunctionCallbackInfo<v8::Value>& info) { v8::Local<v8::Context> context = info.GetIsolate()->GetCurrentContext(); v8::Local<v8::Object> node = Nan::New<v8::Object>(); v8::Local<v8::Array> body = Nan::New<v8::Array>(); v8::Local<v8::ObjectTemplate> objTpl = Nan::New<v8::ObjectTemplate>(); objTpl->Set(Nan::New("foo").ToLocalChecked(), Nan::New<v8::Number>(1)); for (auto i = 0; i < 2000000; i++) { Nan::Set(body, i, objTpl->NewInstance(context).ToLocalChecked()); } node->Set(context, Nan::New("body").ToLocalChecked(), body); info.GetReturnValue().Set(Nan::New<v8::Number>(1)); } void Init(v8::Local<v8::Object> exports, v8::Local<v8::Object> module) { v8::Local<v8::Context> context = exports->CreationContext(); module->Set(context, Nan::New("exports").ToLocalChecked(), Nan::New<v8::FunctionTemplate>(CreateObject) ->GetFunction(context) .ToLocalChecked()); } NODE_MODULE(addon, Init)AKA just return a JsNumber to JavaScript but not the big JsObject, the time cost is:
perf: 756.789msSo it seems like it is not slow that pass big JsObject from native side to JavaScript side in NAN c++ addon.
Conclusion:
- This is the fastest way I have ever found to generate JsObject in Node.js c++ addon: using NAN and
v8::ObjectTemplate. - Big Object(JsArray) passing from c++ addon native side to JavaScript side is not slow(in my case it just cost a few ms).
I am still working on this. I want to found the way to generate JsObject in c++ addon as fast as generate JsObject in pure JavaScript.
Looking forward to your reply :)- This is the fastest way I have ever found to generate JsObject in Node.js c++ addon: using NAN and
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.
I'm also interested in this topic, is there any new clue?
I'm also interested in this topic, is there any new clue?
@Wyctus FYI https://stackoverflow.com/questions/69231342/how-to-generate-jsobject-in-node-js-c-addon-as-fast-as-in-pure-js/69235332?noredirect=1#comment122622138_69235332
I think you would like to read this :)@zhuyingda Wow, this looks really interesting, thank you for mentioning! :)
This issue is stale because it has been open many days with no activity. It will be closed soon unless the stale label is removed or a comment is made.
I am new as a Node.js cpp-addon developer, I need to return a very big Object from cpp(Node.js cpp-addon) to Javascript logic, but I found that it is very slow to generate JsObject in C++ addon rather than pure js.
Here is my code and running result:
cpp-addon case:
Running this code time cost:
pure-js case:
Running this code time cost:
Why is this so slow that generate JsObject in cpp-addon? Perhaps it is relative to V8 Hidden-Class?
How could I generate JsObject in cpp-addon faster?