Home > Blockchain >  Why is the efficiency worse when I use c addon to iterate an array in node.js
Why is the efficiency worse when I use c addon to iterate an array in node.js

Time:03-24

  • 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.

  • Related