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

How Avoiding Arguments Saved The Day

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

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

Avatar for Jan Hecking

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/