C++
node.js Native Extensions
or, what to do when JS is not good enough
Ivan Vergiliev
@IvanVergiliev
Currently helping Chaos Group build a cloud rendering platform
Competitive programming enthusiast
So, we'll talk about performance
...just to keep the stereotype alive
Slides for this talk at
IvanVergiliev.github.io/node-cpp
So, you need more speed?
V8 is really fast
Probably the fastest JavaScript engine nowadays
but it can also be kinda slow
The fastest
JavaScript
engine
Abstractions, GC, dynamic typing, etc. all come at a price
We'll assume that, for some reason, our users need a way to sort a large amount of numbers
How should we do that?
var a = []; for (var i = 0; i < 1000000; ++i) { a[i] = Math.random() } console.time('sort'); a.sort(); console.timeEnd('sort');
In-browser - about 12 seconds
node.js - make it 10s
C++ - 0.8 seconds
before optimizations
Call C++ from Javascript!
A typical CommonJS module
var sort = function (arr) { return arr.sort(); // yeah, cuz that makes sense... } exports.sort = sort;
A basic C++ extension module
#include
#include
using namespace v8; Handle
Sort(const Arguments& args) { HandleScope scope; return Undefined(); } void Init(Handle
exports) { exports->Set(String::NewSymbol("sort"), FunctionTemplate::New(Sort)->GetFunction()); } NODE_MODULE(algortihm, Init)
Build with node-gyp
npm install -g node-gyp
binding.gyp:
{ 'targets': [{ 'target_name': 'algorithm', # must be the same as the one in INIT_MODULE 'sources': [ 'module.cc' ] }] }
node-gyp configure build
results are by default in build/Release/algorithm.node
The sort function
Handle
Sort(const Arguments& args) { HandleScope scope; if (args.Length() < 1 || !args[0]->IsArray()) { ThrowException(Exception::TypeError(String::New("First argument should be an array"))); return Undefined(); } Handle
arr = Handle
::Cast(args[0]); vector
v; for (int i = 0; i < arr->Length(); ++i) { v.push_back(arr->Get(i)->NumberValue()); } sort(v.begin(), v.end()); Handle
res = Array::New(v.size()); for (int i = 0; i < v.size(); ++i) { res->Set(i, Number::New(v[i])); } return scope.Close(res); }
runs in less than a second - that's much better
Are we done?
Would you run this in production?
I hope not.
All JavaScript runs in a single thread, using an event loop managed by
libuv
Our Sort function is not very asynchronous.
Going async
Enter
uv_queue_work
int uv_queue_work( uv_loop_t* loop, uv_work_t* req, uv_work_cb work_cb, uv_after_work_cb after_work_cb);
Async Sort
struct AsyncSortData { vector
v; Persistent
callback; ~AsyncSortData() { callback.Dispose(); } }; Handle
AsyncSort(const Arguments& args) { ... uv_work_t* req = new uv_work_t; AsyncSortData* data = new AsyncSortData; req->data = data; for (int i = 0; i < arr->Length(); ++i) { data->v.push_back(arr->Get(i)->NumberValue()); } data->callback = Persistent
::New(Local
::Cast(args[1])); uv_queue_work(uv_default_loop(), req, sort, afterSort); return Undefined(); }
Async Sort
void sort(uv_work_t* req) { auto data = reinterpret_cast
(req->data); sort(data->v.begin(), data->v.end()); }; void afterSort(uv_work_t* req) { HandleScope scope; auto data = reinterpret_cast
(req->data); Handle
res = Array::New(data->v.size()); for (int i = 0; i < data->v.size(); ++i) { res->Set(i, Number::New(data->v[i])); } data->callback->Call(res, 1, (Handle
[]){res}); delete data; delete req; }
Reasons to avoid native extensions?
Bad for portability
C++ is not as portable as advertised
Harder to maintain
Adds one more language to the codebase
It's ugly
You hate C++ (just guessing)
When to avoid going the C++ way
When you don't have to (duh!)
IO-bound code
Huge number of small function calls
References
NodeJS Docs - Addons
V8 Embedder's Guide
V8 generated docs
Existing native modules
V8 Source Code
←
→
/
Go to slide:
#