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

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).

Ariya Hidayat

April 11, 2013
Tweet

More Decks by Ariya Hidayat

Other Decks in Technology

Transcript

  1. JavaScript Need for Speed
    @ariyahidayat
    San Francisco, April 11, 2013
    1

    View Slide

  2. whoami
    2

    View Slide

  3. Performance Principles
    3

    View Slide

  4. Secrets of High Performance
    Avoid slow code Write fast code
    4

    View Slide

  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

    View Slide

  6. “The Big Rock”
    “First Things First”, Steven Covey
    6

    View Slide

  7. Journey: The Quadrant
    http://ariya.ofilabs.com/2012/11/optimization-journey-vs-destination.html
    7

    View Slide

  8. Under the Hood
    8

    View Slide

  9. JavaScript Engines
    SpiderMonkey Firefox
    JavaScriptCore/Nitro Safari
    V8 Node.js, Chrome
    JScript/Chakra Internet Explorer
    Carakan Opera
    9

    View Slide

  10. Building Blocks
    Virtual
    Machine/
    Interpreter
    Parser
    Runtime
    Source
    Syntax Tree
    Built-in objects,
    host objects, ...
    10

    View Slide

  11. var answer = 42
    Parser
    keyword equal sign
    identifier number
    Variable Declaration
    Identifier
    Literal Constant
    Tokenization →
    Tokens
    Parsing →
    Syntax Tree
    11

    View Slide

  12. Syntax Visualization
    http://esprima.org/demo/parse.html
    12

    View Slide

  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

    View Slide

  14. Execution Steps
    http://toolness.github.io/slowmo-js/
    14

    View Slide

  15. Execution Visualization
    http://int3.github.io/metajs/
    15

    View Slide

  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

    View Slide

  17. Playing with V8
    17

    View Slide

  18. Playing with V8
    git clone git://github.com/v8/v8.git
    cd v8
    make dependencies
    make x64.release -j4
    make x64.debug -j4
    18

    View Slide

  19. Performance Analysis
    19

    View Slide

  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

    View Slide

  21. Stopwatch Measurement
    http://calendar.perfplanet.com/2010/bulletproof-javascript-benchmarks/
    var start = Date.now();
    for (var i = 0; i < 100; i++) {
    doSomething();
    }
    console.log(Date.now() - start, 'ms');
    21

    View Slide

  22. Measurement Confidence
    Accuracy
    Precision
    22

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  26. Run-Time Optimization
    26

    View Slide

  27. What (Not)to Optimize
    Function Calls
    Garbage Collection
    Fixed Object Shape
    Profile-Guided
    27

    View Slide

  28. Function Calls
    28

    View Slide

  29. “Function Call is Expensive”
    sortDepartment
    totalDepartementExpense
    totalEmployeesSalaries
    getEmployeeData
    29

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  36. Fixed Object Shape
    36

    View Slide

  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

    View Slide

  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

    View Slide

  39. Faster Property Access
    Execute the generic
    property access
    color
    speed
    Shape
    check
    Get the value from
    property #1
    console.log(car.color);
    39

    View Slide

  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

    View Slide

  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

    View Slide

  42. Garbage Collector
    42

    View Slide

  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

    View Slide

  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

    View Slide

  45. Profile-Guided
    45

    View Slide

  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

    View Slide

  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

    View Slide

  48. Input Distribution
    54362 Identifier
    10419 Keyword
    8170 String
    5820 Punctuator
    5213 Numeric
    1575 Boolean
    909 Null
    48

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  53. Scalability
    53

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  59. Final Words
    59

    View Slide

  60. Multi-dimensional
    Application revision
    Execution time
    Baseline
    60

    View Slide

  61. http://www.trollquotes.org/5-gandalf-troll-quote/
    61

    View Slide

  62. Measure twice, cut once
    Under the hood: learn & explore
    Be advised of every best practice
    62

    View Slide

  63. Thank You
    [email protected]
    @AriyaHidayat
    ariya.ofilabs.com
    Many artworks are from OpenClipArt
    speakerdeck.com/ariya
    63

    View Slide

  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

    View Slide