node.js: native extension
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.