Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How Avoiding Arguments Saved The Day

How Avoiding Arguments Saved The Day

Performance optimization of functions with variadic arguments

Presented at talk.js Singapore: http://www.meetup.com/Singapore-JS/events/232635728/
Code from the talk: https://gist.github.com/jhecking/2b53141304bf062a1e152bcd671c843f

Jan Hecking

August 25, 2016
Tweet

Other Decks in Programming

Transcript

  1. 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
  2. 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 })
  3. 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
  4. 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) }) }
  5. 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
  6. 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) }
  7. 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) }
  8. 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) }
  9. 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) }
  10. Using ES6 Spread Operator function cbSpreadOperator (cb, err, ...args) {

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

    { if (err && err.code !== 0) return cb(err) cb(null, arg1, arg2, arg3) }
  12. 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)) }
  13. 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) })
  14. 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
  15. 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 })
  16. 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
  17. 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/