Zajímalo mě, co obnáší napsání vlastního addonu (nativního rozšíření) do node.js a jak velký nárůst výkonu to ve výsledku přinese. A protože mám implementovaný stejný sudoku solver jak v JavaScriptu, tak v C, rozhodl jsem se to zjistit.

Hello Honza

Ještě než se pustím do srovnávání a hodnocení, ukážu, jak se takové nativní rozšíření dá napsat:

// hello.js
module.exports.hello = function(name)
{
  return 'hello ' + name;
};

Kód v C++ se stejnou funkcionalitou vypadá takto:

// hello.cc
#include <node.h>;
#include <v8.h>;

using namespace v8;

Handle<Value> hello(const Argument& args)
{
  HandleScope scope;
  return scope.Close(String::Concat(String::New("hello "), args[0]->ToString()));
}

void init(Handle<Object> target)
{
  NODE_SET_METHOD(target, "hello", hello);
}

NODE_MODULE(hello, init)

Začnu od konce, kde to vlastně celé začíná. NODE_MODULE(hello, init) vytvoří třídu dědící od node::ObjectWrap s názvem hello a jako konstruktor bude použita funkce init. Tato definice společně s hlavičkami je minimální nutný předpoklad, aby addon šel úspěšně zkompilovat.

V konstruktoru, nebo lépe inicializační metodě je nutno zaregistrovat veřejné metody, což je přesně to, co dělá řádek NODE_SET_METHOD(target, "hello", hello). Všechny registrované funkce musí vypadat minimálně následovně:

Handle<Value> hello(const Arguments& args) {
  HandleScope scope;
  return scope.Close(Undefined());
}

Kompilace

Pro kompilaci je v současnosti nejlepší použít node-gyp. Stačí vytvořit binding.gyp soubor s následujícím obsahem:

{
  "targets": [
    {
      "target_name": "hello",
      "sources": [ "hello.cc" ]
    }
  ]
}

a následně spustit $ node-gyp clean configure build. Kompilace by měla dopadnout úspěšně, poté lze addon vyzkoušet:

// test.js
var hello = require('./build/Release/hello.node');
console.log(hello.hello('Honza'));

Srovnání

Jak je na příkladu s “Hello Honza” vidět, kód v C++ je na počet znaků mnohem delší a stejně tak jeho napsání zabere víc času. Co se nárůstu výkonu týče, není to nic dramatického. V mém případě jsem ušetřil pouhých 0.0046 sekundy. Jednoduše proto, že JS ↔ C++ boundary is slow to cross.

Můj závěr je, že pokud nepotřebujete více threadů nebo nemáte již existující kód v C/C++, pak byste měli použít JavaScript.