JavaScript: Need for Speed

JavaScript: Need for Speed

http://www.meetup.com/SF-Web-Performance-Group/events/112609542/

Analyzing the performance of large-scale JavaScript applications requires a distinct set of tools. It is no longer enough to run various benchmarks by creating loops and measuring the associated period. The amazing progress of modern JavaScript engines means that developers should not stress about outdated considerations such as cache array length, avoiding 'switch' statements and using 'while' instead of 'for'. Understanding the inner workings of the engine itself will reveal the typical code patterns favored for maximum execution speed (type stability, fixed object shape, profile-guided, garbage minimization, etc).

0284b8950e0f4a57bcc092d4dbb98d97?s=128

Ariya Hidayat

April 11, 2013
Tweet

Transcript

  1. 5.

    Computer Programming as an Art, Knuth’s Turing Award lecture (1974)

    The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming. 5
  2. 11.

    var answer = 42 Parser keyword equal sign identifier number

    Variable Declaration Identifier Literal Constant Tokenization → Tokens Parsing → Syntax Tree 11
  3. 13.

    Execution in Interpreter Tree Visitor Traverse the syntax tree Run

    every syntax node Bytecode Interpreter Traverse and produce bytecodes Execute bytecodes in VM JIT Compiler Generate native machine codes Transfer the control to the codes 13
  4. 16.

    Tiered Execution Baseline Optimized Fast startup Minimal memory usage Decent

    execution speed Intensive initialization Potentially eat more memory Very fast execution button.onclick = function() { doSomething(); } for (i = 0; i < employees.length; ++i) total += employees[i].salary; 16
  5. 20.

    http://ariya.ofilabs.com/2012/12/javascript-performance-analysis-sampling-tracing-and-timing.html Timing Prepare the stopwatch Mark the start and the

    end Sampling Periodically check which function is being executed Tracing Track all function calls and exits 20
  6. 21.
  7. 23.

    Using Benchmark.js var suite = new Benchmark.Suite; suite.add('String#indexOf', function() {

    'Hello World!'.indexOf('o') > -1; }) .on('complete', function() { console.log('Fastest is ' + this.filter('fastest').pluck('name')); }) .run(); http://benchmarkjs.com/ 23
  8. 24.

    Loop-invariant Code Motion What you write Optimized by the engine

    for (var i = 0; i < 100; i++) { sum += Math.sqrt(2) * i; } var temp = Math.sqrt(2); for (var i = 0; i < 100; i++) { sum += temp * i; } Offset → Loss of accuracy 24
  9. 25.

    Dead-Code Elimination http://blogs.msdn.com/b/ie/archive/2010/11/17/html5-and-real-world-site- performance-seventh-ie9-platform-preview-available-for-developers.aspx What you write Optimized by the

    engine function test() { var a = 0, b = 0; for (var i = 0; i < 100; i++) { a += i; b += i * i; } return a; } function test() { var a = 0; for (var i = 0; i < 100; i++) { a += i; } return a; } 25
  10. 30.

    Automatic Inlining What you write Optimized by the engine function

    square(x) { return x * x; } function f(x) { var sum = 0; for (var i = 0; i < x; i++) { sum += square(i); } return sum; } function f(x) { var sum = 0; for (var i = 0; i < x; i++) { sum += i * i; } return sum; } http://ariya.ofilabs.com/2013/04/automatic-inlining-in-javascript-engines.html 30
  11. 31.

    V8 Inlining Tracing http://floitsch.blogspot.com/2012/03/optimizing-for-v8-inlining.html d8 --trace-inlining example.js Did not inline

    InstantiateFunction called from Instantiate (target text too big). Did not inline Instantiate called from Instantiate (target not inlineable). Did not inline called from Instantiate (target not inlineable). Did not inline ConfigureTemplateInstance called from Instantiate (target not inlineable). Did not inline ToObject called from valueOf (target not inlineable). Did not inline FunctionSourceString called from toString (target not inlineable). Did not inline InstantiateFunction called from Instantiate (target text too big). Did not inline Instantiate called from Instantiate (target not inlineable). Did not inline ConfigureTemplateInstance called from Instantiate (target not inlineable). Inlined square called from f. Inlined square called from f. 31
  12. 32.

    Deferred Parsing To further reduce the time to first executed

    instruction, Chakra processes and emits bytecode only for functions that are about to be executed using a mechanism called deferred parsing. http://blogs.msdn.com/b/ie/archive/2012/06/13/advances-in-javascript- performance-in-ie10-and-windows-8.aspx 32
  13. 33.

    Code, Tree, Memory http://ariya.ofilabs.com/2012/07/lazy-parsing-in-javascript-engines.html function add(x, y) { return x

    + y; } function mul(x, y) { return x * y; } alert(add(40, 2)); { "type": "Program", "body": [ { "type": "FunctionDeclaration", "id": { "type": "Identifier", "name": "add" }, "params": [ { "type": "Identifier", "name": "x" }, { "type": "Identifier", "name": "y" } ], "defaults": [], "body": { "type": "BlockStatement", "body": [ { "type": "ReturnStatement", "argument": { "type": "BinaryExpression", "operator": "+", "left": { "type": "Identifier", "name": "x" }, "right": { "type": "Identifier", "name": "y" } } } ] }, "rest": null, "generator": false, "expression": false }, { "type": "FunctionDeclaration", "id": { "type": "Identifier", "name": "mul" }, "params": [ { "type": "Identifier", "name": "x" }, { "type": "Identifier", "name": "y" } ], "defaults": [], "body": { "type": "BlockStatement", "body": [ { "type": "ReturnStatement", "argument": { "type": "BinaryExpression", "operator": "*", "left": { "type": "Identifier", "name": "x" }, "right": { "type": "Identifier", "name": "y" } } } ] }, "rest": null, "generator": false, "expression": false }, { "type": "ExpressionStatement", "expression": { "type": "CallExpression", "callee": { "type": "Identifier", "name": "alert" }, "arguments": [ { "type": "CallExpression", "callee": { "type": "Identifier", "name": "add" }, "arguments": [ { "type": "Literal", "value": 40, "raw": "40" }, { "type": "Literal", "value": 2, "raw": "2" } ] } ] } } ] } { "type": "FunctionDeclaration", "id": { "type": "Identifier", "name": "add" }, "params": [ { "type": "Identifier", "name": "x" }, { "type": "Identifier", "name": "y" } ] }; 33
  14. 34.

    Deep Construction function add(x, y) { return x + y;

    } Declare a function called add. It accepts x and y as the arguments. It has one statement, a return statement. The return value is a binary operation + of x and y. function mul(x, y) { return x * y; } Declare a function called mul. It accepts x and y as the arguments. It has one statement, a return statement. The return value is a binary operation * of x and y. alert(add(40, 2)); Create a function call to alert. The argument is the result of function add with 40 and 2 as the arguments. 34
  15. 35.

    Lazy Construction function add(x, y) { return x + y;

    } Declare a function call add with the function body “{ return x + y; }”. function mul(x, y) { return x * y; } Declare a function call mul with the function body “{ return x * y; }”. alert(add(40, 2)); Create a function call to alert. The argument is the result of function add with 40 and 2 as the arguments. Call function add. Hmm, it is not parsed yet. Call the real parser for “{ return x + y; }”. It accepts x and y as the arguments. The return value is a binary operation + of x and y. 35
  16. 37.

    Shape Transition function Vehicle() { this.color = 'white'; this.speed =

    0; } var car = new Vehicle(); car.color = 'black'; color speed var motorbike = new Vehicle(); motorbike.wheels = ['front', 'back']; car.maker = 'BMW'; color speed maker color speed wheels 37
  17. 38.

    Property Access console.log(car.color); Check if the object has the property

    ‘color’. If it is not found, check the property chain. Get the value of the property. Quiz: What if color does not exist? 38
  18. 39.

    Faster Property Access Execute the generic property access color speed

    Shape check Get the value from property #1 console.log(car.color); 39
  19. 40.

    “Freeze” the Shape http://ariya.ofilabs.com/2012/02/javascript-object-structure-speed-matters.html var universe = { answer: 42

    }; // do something else universe.panic = false; var universe = { answer: 42, panic: true }; // do something else universe.panic = false; Note: Monomorphic inline-cache reduces the impact 40
  20. 41.

    Avoid Conditional Transition var vehicle = { color: 'blue' };

    if (currentYear > 2014) vehicle.backupCamera = new Camera(); var vehicle = { color: 'blue', backupCamera: null }; if (currentYear > 2014) vehicle.backupCamera = new Camera(); 41
  21. 43.

    Minimize Object Construction http://www.youtube.com/watch?v=Vo72W1HWeFI var tick = new Date(); for

    (var i = 0, j = 0; i < 4e4; ++i) { var delta = new Date(); delta = delta - tick; tick = new Date(); j += delta; } var tick = Date.now(); for (var i = 0, j = 0; i < 4e4; ++i) { var delta = Date.now() - tick; tick = Date.now(); j += delta; } A lot of small Date objects 43
  22. 44.

    V8 Garbage Collector Tracing d8 --trace-gc example.js new Date() Date.now()

    56 ms: Scavenge 1.8 (36.0) -> 0.9 (36.0) MB, 2.8 ms 73 ms: Scavenge 1.8 (36.0) -> 0.9 (37.0) MB, 2.8 ms 89 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 1.0 ms 109 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 1.0 ms 126 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 0.9 ms 141 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 0.9 ms 159 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 0.9 ms 176 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 1.0 ms 192 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 0.9 ms 207 ms: Scavenge 1.9 (37.0) -> 0.9 (37.0) MB, 1.0 ms 54 ms: Scavenge 1.8 (36.0) -> 0.9 (36.0) MB, 2.9 ms 67 ms: Scavenge 1.8 (36.0) -> 0.9 (37.0) MB, 2.7 ms. 44
  23. 46.

    Classifier Task Array [1, 2, 3] Object { a: 1,

    b: 3 } Expression in brackets (x + y) Function function test() { } Identifier x Literal 42 ? 46
  24. 47.

    Naive Classifier function parsePrimaryExpression() { if (match('[')) return parseArrayInitialiser(); if

    (match('{')) return parseObjectInitialiser(); if (match('(')) return parseBracketExpression(); if (matchKeyword('function')) return parseFunctionExpression() if (matchKeyword('this')) return createThisExpression(); if (match('/') || match('/=')) return createRegExpLiteral(); var token = lex(); if (token.type === Token.Identifier) return createIdentifier(token); if (token.type === Token.NullLiteral) return createNullLiteral(); if (token.type === Token.NumericLiteral) return createNumericLiteral(token); if (token.type === Token.StringLiteral) return createStringLiteral(token); if (token.type === Token.BooleanLiteral) return createBooleanLiteral(token); return throwUnexpected(token); } 47
  25. 49.

    Optimized Classifier function parsePrimaryExpression() { var token = lookahead(); if

    (token.type === Token.Identifier) return createIdentifier(token); if (token.type === Token.NumericLiteral) return createNumericLiteral(token); if (token.type === Token.StringLiteral) return createStringLiteral(token); if (matchKeyword('this')) return createThisExpression(); if (matchKeyword('function')) return parseFunctionExpression() if (token.type === Token.BooleanLiteral) return createBooleanLiteral(token); if (token.type === Token.NullLiteral) return createNullLiteral(); if (match('[')) return parseArrayInitialiser(); if (match('{')) return parseObjectInitialiser(); if (match('(')) return parseBracketExpression(); if (match('/') || match('/=')) return createRegExpLiteral(); return throwUnexpected(lex()); } 49
  26. 50.

    Short Circuit http://ariya.ofilabs.com/2011/11/matching-a-decimal-digit.html function isDigit(ch) { return '0123456789'.indexOf(ch) >= 0;

    } function isDigit(ch) { return ch !==' ' && '0123456789'.indexOf(ch) >= 0; } Letters Digits Spaces 50
  27. 51.

    Object in a Set http://ariya.ofilabs.com/2012/08/determining-objects-in-a-set-examples-in-javascript.html var valid_words = { 'foobar':

    true, 'bar': true, 'baz': true, 'quux': true }; function is_valid(word) { return valid_words.hasOwnProperty(word); } is_valid('fox'); // false Dictionary Spell checker Alternatives: array lookup, switch case, prefix/suffix tree, perfect hash 51
  28. 52.

    Tiered Conditionals function is_valid(word) { switch (word.length) { case 3:

    return word === 'bar' || word === 'baz'; case 4: return word === 'quux'; case 6: return word === 'foobar'; } return false; } Filter #1: Length http://ariya.ofilabs.com/2012/08/determining-objects-in-a-set-examples-in-javascript.html 52
  29. 54.

    Fast = Enough? Alice Bob Chuck Dan ... Bob Alice

    Dan Chuck ... Address Book Application Sort How’s the speed? 2 ms to sort 10 contacts 54
  30. 55.

    The Code Array.prototype.swap = function (i, j) { var k

    = this[i]; this[i] = this[j]; this[j] = k; } function sort(list) { var items = list.slice(0), swapped = false, p, q; for (p = 1; p < items.length; ++p) { for (q = 0; q < items.length - p; ++q) { if (items[q + 1] < items[q]) { items.swap(q, q + 1); swapped =true; } } if (!swapped) break; } return items; } Bubble Sort ??? 55
  31. 56.

    Function Instrumentation Array.prototype.swap = function (i, j) { var k

    = this[i]; this[i] = this[j]; this[j] = k; } Array.prototype.swap = function (i, j) { Log({ name: 'Array.prototype.swap', lineNumber: 1}); var k = this[i]; this[i] = this[j]; this[j] = k; } http://ariya.ofilabs.com/2012/01/scalable-web-apps-the-complexity-issue.html 56
  32. 57.

    Run-time Analysis 0 250000 500000 0 500 1000 Calls to

    swap() Input Size http://ariya.ofilabs.com/2012/01/scalable-web-apps-the-complexity-issue.html 57
  33. 58.

    Start-up Execution Tracking https://gist.github.com/1823129 jQuery Mobile startup log 4640 function

    calls jquery.js 26 jQuery jquery.js 103 init undefined, undefined, [object Object] jquery.js 274 each (Function) jquery.js 631 each [object Object], (Function), undefined jquery.js 495 isFunction [object Object] jquery.js 512 type [object Object] jquery.mobile.js 1857 [Anonymous] jquery.mobile.js 642 [Anonymous] jquery.mobile.js 624 enableMouseBindings jquery.mobile.js 620 disableTouchBindings http://ariya.ofilabs.com/2012/02/tracking-javascript-execution-during-startup.html 58
  34. 62.

    Measure twice, cut once Under the hood: learn & explore

    Be advised of every best practice 62
  35. 64.

    More Stuff Vyacheslav Egorov http://mrale.ph/ Florian Loitsch http://floitsch.blogspot.com/ Andy Wingo

    http://wingolog.org/ http://blip.tv/jsconf/jsconf2012-andy-wingo-6139109 Michael Starzinger: The Footprint of Performance http://www.youtube.com/watch?v=ZhshEZIV2F4 Google I/O 2012 - Breaking the JavaScript Speed Limit with V8 http://www.youtube.com/watch?v=UJPdhx5zTaw 64