node.js为什么用c++插件迭代数组效率变差了

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

// 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('--------------')
}
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
--------------

(此处为 V8 开发人员。)
这是意料之中的。原因是跨越 C++ 和 JavaScript 之间的边界(在任一方向)都比较昂贵。这就是为什么在 V8 中,我们不在 C++ 中实现 Array.map 和类似的 built-in 函数;然而,我们用来实现这一点的内部技术(“CodeStubAssembler”、“Torque”)不适用于 Node 插件。

It's really that the for loop cost a long time, but why?

这不是 for 循环本身,而是 cb.Call(...) 表达式。

Shouldn't c++ faster than js?

不,不一定。优化后的 JS 可以一样快。在极少数情况下,当您创建一个动态优化比静态优化更强大的场景时,它甚至可以更快。在手头的例子中,这与哪种语言更快无关,而是关于 cross-language 函数调用。

And is there any way to improve my addon efficiency?

写在JavaScript。或者找到一种方法来减少任一方向上任何回调的调用(即既不从 C++ 为每个数组元素调用 JS,也不从 JS 为每个数组元素调用 C++)。对于数字代码,TypedArrays 有时对此很有用,具体取决于您的用例。


旁注:为了更公平的比较,基于普通旧 JavaScript for 循环的第三种情况应该预分配结果数组,即将 const c = [] 替换为 const c = new Array(arr3.length),以及 c.pushc[i] = 。这样速度会快一倍。