A journey through the wonderful world of Node.js C++ addons. This talk was given at the September 8, 2015 NodeMN meetup.
Code: https://github.com/cb1kenobi/nodemn
2. About Me
• Lead Software Engineer at
Appcelerator
• JavaScript hacker since 2000
• Open source
o Titanium
o Dojo Toolkit
• @cb1kenobi
o https://github.com/cb1kenobi
o https://twitter.com/cb1kenobi
3. My Projects
• node-ios-device
o https://www.npmjs.com/package/node-ios-device
o Talks to Apple’s MobileDevice framework to query iOS devices, install
apps, and tail syslog
• lcdstats
o https://www.npmjs.com/package/lcdstats
o Prints system info to LCD screen using USB
8. Example Addon
• example.cpp
o Your C++ file
• binding.gyp
o GYP file that says what and how your C++ should be compiled
• package.json
o “gypfile”: true
o Tells NPM to run node-gyp
14. GYP
• Generate Your Projects
• https://code.google.com/p/gyp/
• Written in Python (requires 2.7)
• Generates projects:
o Makefiles, Xcode, Visual Studio, etc
• .gyp file
o JSON + comments + double or single quotes + trailing commas
• Features:
o Variables, includes, defaults, targets, dependencies, conditions, actions
• Use to be used for V8 and Chrome
o Now replaced by gn
o V8 still ships a GYP file
29. Build & Run
$ node –v
0.12.7
$ node
> var example2 = require('./build/Release/example2.node')
Error: Module did not self-register.
at Error (native)
at Module.load (module.js:355:32)
at Function.Module._load (module.js:310:12)
at Module.require (module.js:365:17)
at require (module.js:384:17)
at repl:1:16
at REPLServer.defaultEval (repl.js:132:27)
at bound (domain.js:254:14)
at REPLServer.runBound [as eval] (domain.js:267:12)
at REPLServer.<anonymous> (repl.js:279:12)
30. Why?
• example2.node is compiled for Node.js 0.10.40
o API Version 11
• Node.js 0.12 is API Version 14
31. Addon Compatibility
• API Version
• Node.js version
o 0.11 -> 0.12
• Platform (OS X, Linux, Windows)
• Architecture (32 vs 64-bit)
32. Solutions
• Perfect solution?
o Doesn't exist today
• Pre-build binaries in NPM package
o Pros:
• Users always have a compatible addon ready to go
• Users don't have to have C++ toolchain installed
• Redistributable without needed Internet connection
o Cons:
• Continually larger and larger package file size
• Need to publish new binary for every new Node.js/io.js version
• Need to manage version paths yourself
• node-pre-gyp
o Pros:
• Dynamically downloads binary at runtime; fallback to local build
• Users don't have to have C++ toolchain installed unless required version does
not exist
• Small package size; no binaries in NPM
• Automatically manages version paths
o Cons:
• Need to publish new binary for every new Node.js/io.js version
• Users either need an Internet connection or C++ toolchain
33. Only a partial solution
• New Node.js/io.js releases all the time
• Neglected addons
• Some environments possibly untested
• No way to tell NPM what platforms the addon is
targeting
o NPM just tries to build it
35. August 2014
• 90,984 packages
• 66,775 on Github
• 860 are C++ addons
o 1.3% of all NPM packages that are on Github are C++ addons
0.10.30 0.11.13
OS X
Linux
Windows
473 (55%) 126 (14.7%)
417 (48.5%) 129 (15%)
295 (34.3%) 111 (12.9%)
36. September 2015
• 193,225 packages
• 141,953 on Github
• 1,241 are C++ addons
o 0.87% of all NPM packages that are on Github are C++ addons
• ~30% of all NPM packages depend on a C++ addon
o https://medium.com/node-js-javascript/4-0-is-the-new-1-0-386597a3436d
• Note: some C++ addons switched to pure JS impl
0.10.40 0.12.7 4.0.0
OS X
Didn't have time to run tests on Linux & Windows
729 (58.7%) 513 (41.3%) 237 (19.1%)
37. Addon State Summary
August 2014 September 2015 Growth
# packages 90,984 193,225 2.12x
# on Github 66,775 141,953 2.13x
# C++ addons 860 (1.3%) 1,241 (0.87%) 1.44x
August 2014 September 2015
0.10.30 0.11.13 0.10.40 0.12.7 4.0.0
OS X 473 (55%) 126 (14.7%) 729 (58.7%) 513 (41.3%) 237 (19.1%)
Linux 417 (48.5%) 129 (15%) ? ? ?
Windows 295 (34.3%) 111 (12.9%) ? ? ?
43. C++ Addon Public API
• Use an index.js to expose a high-level public API
o Things are generally easier in JavaScript
• C++ addon implements low-level API
o Fancy APIs are a lot of work to build in C++
50. Threading
• Node.js is single threaded
• Your C++ code can block your Node app
• Use libuv for async tasks
• uv_queue_work() makes async easy(ier)
• No V8 access from worker threads
• Threads can have their own event loops
51. C++ ⟷ JavaScript Bridge
#include <nan.h>
#include <node.h>
#include <math.h>
using namespace v8;
NAN_METHOD(crunch) {
double n = floor(133.7 / 3.14159265359);
info.GetReturnValue().Set(Nan::New<Number>(n));
}
NAN_MODULE_INIT(init) {
Nan::Set(target,
Nan::New("crunch").ToLocalChecked(),
Nan::GetFunction(Nan::New<FunctionTemplate>(crunch))
.ToLocalChecked()
);
}
NODE_MODULE(example4, init)
function jsCrunch() {
return Math.floor(133.7 / Math.PI);
}
var start = new Date;
for (var i = 0; i < 1e8; i++) {
jsCrunch();
}
var jsDelta = (new Date) - start;
var nativeCrunch = require(
'./build/Release/example4.node').crunch;
start = new Date;
for (var i = 0; i < 1e8; i++) {
nativeCrunch();
}
var nativeDelta = (new Date) - start;
console.log('JS took ' + jsDelta + 'ms');
console.log('Native took ' + nativeDelta + 'ms');
Borrowed from http://kkaefer.github.io/node-cpp-modules
52. C++ ⟷ JavaScript Bridge
$ node index.js
JS took 48ms
Native took 9397ms
53. JavaScript vs C++
• V8 aggressively JITs JavaScript code
o Profiles and compiles it into native instructions
• V8 is really fast
o Object allocation
• Somethings you just can't do in JavaScript
• Really depends on the use case and developer skill
o Leaving it up to you to decide
o Perform your own benchmarks
55. Summary
• Prefer a pure JavaScript solution over a C++ solution
o Unless A) you have to or B) you need performance
• Use nan
• Highly consider using node-pre-gyp + continuous
build server
• Use a JavaScript wrapper to implement public API
• Don't block the main thread
o Use libuv to perform async operations