Slide 1

Slide 1 text

How Avoiding Arguments Saved the Day (And improved benchmark performance by 10%!)

Slide 2

Slide 2 text

No content

Slide 3

Slide 3 text

Node.js Client v2 Performance • Use of async I/O in the underlying C client Usability / Maintainability • Adopt Node.js callback conventions • Shift more logic from C to JS layer

Slide 4

Slide 4 text

Performance v1.x using sync I/O ~55k TPS v2.0-pre1 using async I/O ~79k TPS

Slide 5

Slide 5 text

v1.x using sync I/O ~55k TPS v2.0-pre1 using async I/O ~79k TPS Performance +40%

Slide 6

Slide 6 text

v1 Callbacks • DB request are handled directly by the C layer • Callback receives status object, e.g. { code: 1 } • Application expected to check status code Client.prototype.get = function (key, policy, callback) { if (typeof policy === 'function') { callback = policy policy = null } else if (typeof callback !== 'function') { throw new TypeError('"callback" must be a function') } this.as_client.get(key, policy, callback) } client.get(key, function (status, record) { if (status.code != 0) { throw new Error(status.message) } // process record })

Slide 7

Slide 7 text

v2 Callbacks function callbackHandler (callback, err) { if (err && err.code !== as.status.AEROSPIKE_OK) { callback(AerospikeError.fromASError(err)) } else { var args = Array.prototype.slice.call(arguments, 2) args.unshift(null) callback.apply(undefined, args) } } Client.prototype.get = function (key, policy, callback) { if (typeof policy === 'function') { callback = policy policy = null } else if (typeof callback !== 'function') { throw new TypeError('"callback" must be a function') } this.as_client.getAsync(key, policy, function (err, record, metadata) { callbackHandler(callback, err, record, metadata, key) }) } client.get(key, function (err, record) { if (err) throw err // process record }) • Error-first callbacks following Node.js conventions • Null is passed in case operation is successful

Slide 8

Slide 8 text

Usability v2.0-pre1 thin JS wrapper ~78k TPS v2.0-pre2 impr. callbacks ~70k TPS

Slide 9

Slide 9 text

v2.0-pre1 thin JS wrapper ~78k TPS v2.0-pre2 impr. callbacks ~70k TPS Usability -10%

Slide 10

Slide 10 text

v2 Callbacks function callbackHandler (callback, err) { if (err && err.code !== as.status.AEROSPIKE_OK) { callback(AerospikeError.fromASError(err)) } else { var args = Array.prototype.slice.call(arguments, 2) args.unshift(null) callback.apply(undefined, args) } } Client.prototype.get = function (key, policy, callback) { if (typeof policy === 'function') { callback = policy policy = null } else if (typeof callback !== 'function') { throw new TypeError('"callback" must be a function') } this.as_client.getAsync(key, policy, function (err, record, metadata) { callbackHandler(callback, err, record, metadata, key) }) }

Slide 11

Slide 11 text

Handling Variadic Function Arguments • Use Array#slice • Use the “despised”[1] Array constructor • Iterating through arguments • ES6: Use the new spread operator • Or is there a better, naive solution? [1] MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments

Slide 12

Slide 12 text

Using Array#slice function cbSliceArguments (cb, err) { if (err && err.code !== 0) return cb(err) var slice = Array.prototype.slice var args = slice.call(arguments, 2) args.unshift(null) // set err to null cb.apply(undefined, args) }

Slide 13

Slide 13 text

Using Array Constructor function cbArrayConstructor (cb, err) { if (err && err.code !== 0) return cb(err) var args = Array.apply(null, arguments) args.shift() // remove callback argument args[0] = null // set err to null cb.apply(undefined, args) }

Slide 14

Slide 14 text

Using Array Iteration function cbIterateArguments (cb, err) { if (err && err.code !== 0) return cb(err) var len = arguments.length var args = [null] for (var i = 2; i < len; i++) { args[i - 1] = arguments[i] } cb.apply(undefined, args) }

Slide 15

Slide 15 text

Iterate & Push function cbIterateArgumentsWithPush (cb, err) { if (err && err.code !== 0) return cb(err) var len = arguments.length var args = [null] for (var i = 2; i < len; i++) { args.push(arguments[i]) } cb.apply(undefined, args) }

Slide 16

Slide 16 text

Using ES6 Spread Operator function cbSpreadOperator (cb, err, ...args) { if (err && err.code !== 0) return cb(err) cb(null, ...args) }

Slide 17

Slide 17 text

Avoid Variadic Arguments function cbNaive (cb, err, arg1, arg2, arg3) { if (err && err.code !== 0) return cb(err) cb(null, arg1, arg2, arg3) }

Slide 18

Slide 18 text

V8 Optimization function status(f) { switch(%GetOptimizationStatus(f)) { case 1: return 'optimized' case 2: return 'not optimized' case 3: return 'always optimized' case 4: return 'never optimized' case 6: return 'maybe deoptimized' case 7: return 'optimized by TurboFan' default: 'unknown' } } function printStatus(f) { console.log('# Function %s is %s', f.name, status(f)) }

Slide 19

Slide 19 text

V8 Optimization var handlers = require('./callback-handlers') cb = function () {} error = { code: 0 } result = 'some value' handlers.forEach((f) => { // 2 calls are needed to go from // uninitialized -> pre-monomorphic -> monomorphic f(cb, error, result) f(cb, error, result) ;%OptimizeFunctionOnNextCall(f) f(cb, error, result) printStatus(f) })

Slide 20

Slide 20 text

V8 Optimization $ node -v v6.4.0 $ node --trace_opt --trace_deopt --allow-natives-syntax optimize.js | grep "^#" # Function cbSliceArguments is not optimized # Function cbArrayConstructor is optimized # Function cbIterateArguments is optimized # Function cbIterateArgumentsUsingPush is not optimized # Function cbSpreadOperator is not optimized # Function cbNaive is optimized

Slide 21

Slide 21 text

Let's Benchmark! var suite = new require('benchmark').Suite var handlers = require('./callback-handlers') // add tests handlers.forEach(function (f) { suite.add(f.name, function () { f(() => {}, { code: 0 }, 'async result') }) }) // add listeners and run async suite.on('cycle', function (event) { console.log(String(event.target)) }).on('complete', function () { console.log('And the winner is... ' + suite.filter('fastest').map('name') + '!') }).run({ async: true })

Slide 22

Slide 22 text

Benchmark Results CB Handler Ops/Second cbSliceArguments 1,230,235 cbArrayConstructor 555,189 cbIterateArguments 4,732,473 cbIterateArgumentsUsingPush 5,360,134 cbSpreadOperator 1,704,605 cbNaive 59,321,569

Slide 23

Slide 23 text

About Me Jan Hecking [email protected] linkedin.com/in/jhecking @jhecking jhecking

Slide 24

Slide 24 text

References • Slides:
 https://speakerdeck.com/jhecking/how-avoiding-arguments-saved-the-day • Source code:
 https://gist.github.com/jhecking/2b53141304bf062a1e152bcd671c843f • Original blog post:
 http://www.aerospike.com/blog/node-on-fire/ • Aerospike Node.js Client:
 https://github.com/aerospike/aerospike-client-nodejs • Bluebird project Optimization Killers wiki:
 https://github.com/petkaantonov/bluebird/wiki/Optimization-killers • MDN articles on the arguments object:
 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments • talk.js meetup:
 http://www.meetup.com/Singapore-JS/events/232635728/