I'm a beginner at node.js c addon, and I'm trying to implement a c addon that does the same thing as Array.prototype.map function.
But after I finished this, I benchmarked my addon and found it's 100 times worse than the Array.prototype.map function. And it's even worse than I used for loop directly in js.
Here's my code:
// addon.cc
#include <napi.h>
Napi::Value myFunc(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Array arr = info[0].As<Napi::Array>();
Napi::Function cb = info[1].As<Napi::Function>();
// iterate over array and call callback for each element
for (uint32_t i = 0; i < arr.Length(); i ) {
arr[i] = cb.Call(env.Global(), {arr.Get(i)});
}
return arr;
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
return Napi::Function::New(env, myFunc);
}
NODE_API_MODULE(addon, Init)
var addon = require("bindings")("addon")
for (let i = 0; i < 10; i ) {
const arr1 = [];
while (arr1.length < 10000000) {
arr1.push(Math.random());
}
const arr2 = [];
while (arr2.length < 10000000) {
arr2.push(Math.random());
}
const arr3 = [];
while (arr3.length < 10000000) {
arr3.push(Math.random());
}
console.time("map");
const a = arr1.map((cur) => cur * 2);
console.timeEnd("map");
console.time("myAddon");
const b = addon(arr2, (i) => i * 2);
console.timeEnd("myAddon");
const c = [];
console.time('for');
for (let i = 0; i < arr3.length; i ) {
c.push(arr3[i] * 2);
}
console.timeEnd('for');
console.log('--------------')
}
- And my benchmark result:
map: 411.9ms
myAddon: 3.220s
for: 218.143ms
--------------
map: 363.966ms
myAddon: 2.841s
for: 86.077ms
--------------
map: 481.605ms
myAddon: 2.819s
for: 75.333ms
--------------
- I tried to time my addon and return the execution time during the
for
loop in the c code, return it to js and output the value. - It's really that the
for
loop cost a long time, but why? Shouldn't c faster than js? - And is there any way to improve my addon efficiency?
CodePudding user response:
(V8 developer here.)
This is expected. The reason is that crossing the boundary between C and JavaScript (in either direction) is comparatively expensive. That's why in V8, we don't implement Array.map
and similar built-in functions in C ; however the internal techniques we use to accomplish that ("CodeStubAssembler", "Torque") aren't available to Node addons.
It's really that the for loop cost a long time, but why?
It's not the for
-loop itself, it's the cb.Call(...)
expression.
Shouldn't c faster than js?
No, not necessarily. Optimized JS can be just as fast. In very rare cases it can even be faster, when you create a scenario where dynamic optimizations are more powerful than static optimizations. In the case at hand, it's not about which language is faster though, it's about the cross-language function calls.
And is there any way to improve my addon efficiency?
Write it in JavaScript. Or find a way to have fewer invocations of any callbacks in either direction (i.e. neither calling JS from C for every array element nor calling C from JS for every array element). For numerical code, TypedArrays can sometimes be useful for this, depending on your use case.
Side note: for a fairer comparison, the third case based on a plain old JavaScript for
loop should preallocate the result array, i.e. replace const c = []
with const c = new Array(arr3.length)
, and c.push
with c[i] =
. It'll be twice as fast that way.