Pro Yearly is on sale from $80 to $50! »

V8 internals for JavaScript developers @ Fronteers 2018

V8 internals for JavaScript developers @ Fronteers 2018

This presentation demonstrates how learning just a little bit about JavaScript engine internals can help you improve the run-time performance of your JavaScript code — not just in V8 specifically, but across all JavaScript engines!

Video: TBD

A written version is available here: https://mths.be/v8ek

24e08a9ea84deb17ae121074d0f17125?s=128

Mathias Bynens

October 05, 2018
Tweet

Transcript

  1. @mathias V8 internals for JavaScript developers @mathias // @v8js

  2. Write modern, idiomatic JavaScript, and let the JavaScript engine worry

    about making it fast.
  3. @mathias JavaScript source code

  4. @mathias parser JavaScript source code

  5. @mathias parser Abstract Syntax Tree JavaScript source code

  6. @mathias parser Abstract Syntax Tree interpreter JavaScript source code

  7. @mathias parser Abstract Syntax Tree interpreter bytecode JavaScript source code

  8. @mathias optimizing compiler parser Abstract Syntax Tree interpreter bytecode JavaScript

    source code optimize + profiling data
  9. @mathias optimized code optimizing compiler parser Abstract Syntax Tree interpreter

    bytecode JavaScript source code optimize + profiling data
  10. @mathias optimized code optimizing compiler parser Abstract Syntax Tree interpreter

    bytecode JavaScript source code deoptimize optimize + profiling data
  11. @mathias optimized code optimizing compiler parser Abstract Syntax Tree interpreter

    bytecode JavaScript source code deoptimize optimize + profiling data
  12. @mathias optimized code optimizing compiler parser Abstract Syntax Tree interpreter

    bytecode JavaScript source code deoptimize optimize + profiling data
  13. @mathias optimized code optimizing compiler parser Abstract Syntax Tree interpreter

    bytecode JavaScript source code deoptimize optimize + profiling data
  14. @mathias optimized code optimizing compiler parser Abstract Syntax Tree interpreter

    bytecode JavaScript source code deoptimize optimize + profiling data optimized code optimizing compiler !"❤$%&'()"!
  15. @mathias Elements kinds in V8

  16. @mathias const object = { // ‚* foo: true, //

    ‚* // ‚* // ‚* // ‚* }; // ‚*
  17. @mathias const object = { // ‚* 'foo': true, //

    ‚* // ‚* // ‚* // ‚* }; // ‚*
  18. @mathias const object = { // ‚* 'foo': true, //

    ‚* '#fronteers': true, // ‚* // ‚* // ‚* }; // ‚*
  19. @mathias const object = { // ‚* 'foo': true, //

    ‚* '#fronteers': true, // ‚* 'Iñtërnâtiônàlizætiøn‚*': true, // ‚* }; // ‚*
  20. @mathias const object = { // ‚* 'foo': true, //

    ‚* '#fronteers': true, // ‚* 'Iñtërnâtiônàlizætiøn‚*': true, '123': true, // ‚* }; // ‚*
  21. @mathias Elements kinds in V8

  22. @mathias const array = [1, 2, 3];


  23. @mathias const array = [1, 2, 3];
 // elements kind:

    PACKED_SMI_ELEMENTS

  24. @mathias const array = [1, 2, 3];
 // elements kind:

    PACKED_SMI_ELEMENTS
 array.push(4.56);

  25. @mathias const array = [1, 2, 3];
 // elements kind:

    PACKED_SMI_ELEMENTS
 array.push(4.56);
 // elements kind: PACKED_DOUBLE_ELEMENTS
  26. @mathias const array = [1, 2, 3];
 // elements kind:

    PACKED_SMI_ELEMENTS
 array.push(4.56);
 // elements kind: PACKED_DOUBLE_ELEMENTS
 array.push('x');

  27. @mathias const array = [1, 2, 3];
 // elements kind:

    PACKED_SMI_ELEMENTS
 array.push(4.56);
 // elements kind: PACKED_DOUBLE_ELEMENTS
 array.push('x');
 // elements kind: PACKED_ELEMENTS
  28. @mathias Smi Doubles Regular elements Elements kinds

  29. @mathias const array = [1, 2, 3];
 // elements kind:

    PACKED_SMI_ELEMENTS
 array.push(4.56);
 // elements kind: PACKED_DOUBLE_ELEMENTS
 array.push('x');
 // elements kind: PACKED_ELEMENTS
  30. @mathias array.length; // 5
 index 0 1 2 3 4

    value 1 2 3 4.56 'x'
  31. @mathias array.length; // 5
 array[9] = 1; // array[5] until

    array[8] are now holes
 index 0 1 2 3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  32. @mathias array.length; // 5
 array[9] = 1; // array[5] until

    array[8] are now holes
 // elements kind: HOLEY_ELEMENTS index 0 1 2 3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  33. @mathias array[8];
 // " ??? index 0 1 2 3

    4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  34. @mathias array[8];
 // " ??? ❌ index 0 1 2

    3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  35. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true index 0 1 2 3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  36. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true ❌ index 0 1 2 3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  37. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true ❌ hasOwnProperty(array, '8');
 // " false index 0 1 2 3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  38. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true ❌ hasOwnProperty(array, '8');
 // " false ❌ index 0 1 2 3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  39. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true ❌ hasOwnProperty(array, '8');
 // " false ❌ hasOwnProperty(Array.prototype, '8');
 // " false
  40. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true ❌ hasOwnProperty(array, '8');
 // " false ❌ hasOwnProperty(Array.prototype, '8'); // " false ❌
  41. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true ❌ hasOwnProperty(array, '8');
 // " false ❌ hasOwnProperty(Array.prototype, '8'); // " false ❌ hasOwnProperty(Object.prototype, '8'); // " false
  42. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

    8 < array.length; // bounds check // " true ❌ hasOwnProperty(array, '8');
 // " false ❌ hasOwnProperty(Array.prototype, '8'); // " false ❌ hasOwnProperty(Object.prototype, '8'); // " false ✅
  43. @mathias array[8];
 // " undefined ✅
 8 >= 0 &&

    8 < array.length; // bounds check // " true hasOwnProperty(array, '8');
 // " false hasOwnProperty(Array.prototype, '8'); // " false hasOwnProperty(Object.prototype, '8'); // " false ✅
  44. @mathias packedArray[8];
 // " undefined ✅
 8 >= 0 &&

    8 < packedArray.length; // bounds check // " true ✅ hasOwnProperty(packedArray, '8');
 // " true ✅ hasOwnProperty(Array.prototype, '8'); // " false ✅ hasOwnProperty(Object.prototype, '8'); // " false ✅
  45. @mathias packedArray[8];
 // " undefined ✅
 8 >= 0 &&

    8 < packedArray.length; // bounds check // " true ✅ hasOwnProperty(packedArray, '8');
 // " true ✅ hasOwnProperty(Array.prototype, '8'); // " false ✅ hasOwnProperty(Object.prototype, '8'); // " false ✅
  46. @mathias array[0];
 // " ???


  47. @mathias array[0];
 // " ??? ❌

  48. @mathias array[0];
 // " ??? ❌ 0 >= 0 &&

    0 < array.length; // bounds check // " true
  49. @mathias array[0];
 // " ??? ❌ 0 >= 0 &&

    0 < array.length; // bounds check // " true ❌
  50. @mathias array[0];
 // " ??? ❌ 0 >= 0 &&

    0 < array.length; // bounds check // " true ❌
 hasOwnProperty(array, '0');
 // " true
  51. @mathias array[0];
 // " ??? ❌ 0 >= 0 &&

    0 < array.length; // bounds check // " true ❌
 hasOwnProperty(array, '0');
 // " true ✅
  52. @mathias array[0];
 // " 1 ✅ 0 >= 0 &&

    0 < array.length; // bounds check // " true
 hasOwnProperty(array, '0');
 // " true ✅
  53. @mathias PACKED > HOLEY

  54. @mathias PACKED > HOLEY -. /0

  55. @mathias Smi Doubles Regular elements Elements kinds

  56. @mathias Smi, packed Doubles, packed Regular elements, packed Smi, holey

    Doubles, holey Regular elements, holey
  57. lattice

  58. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS

  59. @mathias Array.prototype.forEach PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Chrome 59

  60. @mathias Array.prototype.forEach PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Chrome 61

  61. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array.prototype.forEach Chrome 64

  62. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array.prototype.map Chrome 64

  63. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array.prototype.filter Chrome 64

  64. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array.prototype.some Chrome 64

  65. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array.prototype.every Chrome 64

  66. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array.prototype.reduce Chrome 64

  67. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array.prototype.reduceRight Chrome 64

  68. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array#{find,findIndex} 1 Chrome

    64
  69. @mathias

  70. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS Array#{find,findIndex} Chrome 70

  71. @mathias const array = new Array(3);


  72. @mathias const array = new Array(3);
 index 0 1 2

    value
  73. @mathias const array = new Array(3);
 // HOLEY_SMI_ELEMENTS
 index 0

    1 2 value
  74. @mathias const array = new Array(3);
 // HOLEY_SMI_ELEMENTS
 array[0] =

    'a';
 index 0 1 2 value 'a'
  75. @mathias const array = new Array(3);
 // HOLEY_SMI_ELEMENTS
 array[0] =

    'a';
 // HOLEY_ELEMENTS index 0 1 2 value 'a'
  76. @mathias const array = new Array(3);
 // HOLEY_SMI_ELEMENTS
 array[0] =

    'a';
 // HOLEY_ELEMENTS array[1] = 'b';
 index 0 1 2 value 'a' 'b'
  77. @mathias const array = new Array(3);
 // HOLEY_SMI_ELEMENTS
 array[0] =

    'a';
 // HOLEY_ELEMENTS array[1] = 'b';
 array[2] = 'c';
 index 0 1 2 value 'a' 'b' 'c' 2 now packed! 2
  78. @mathias const array = new Array(3);
 // HOLEY_SMI_ELEMENTS
 array[0] =

    'a';
 // HOLEY_ELEMENTS array[1] = 'b';
 array[2] = 'c';
 // HOLEY_ELEMENTS (still!) 2 now packed! 2 but it’s too late 3 index 0 1 2 value 'a' 'b' 'c'
  79. @mathias const array = ['a', 'b', 'c'];
 // elements kind:

    PACKED_ELEMENTS

  80. @mathias const array = ['a', 'b', 'c'];
 // elements kind:

    PACKED_ELEMENTS
 
 // …
 array.push(someValue);
 array.push(someOtherValue);
  81. @mathias Avoid holes! #ProTip Avoid holes

  82. @mathias for (let i = 0, item; (item = items[i])

    != null; i++) {
 doSomething(item);
 }
  83. @mathias for (let i = 0, item; (item = items[i])

    != null; i++) {
 doSomething(item);
 }
  84. @mathias for (let i = 0, item; (item = items[i])

    != null; i++) {
 doSomething(item);
 } 
 for (let index = 0; index < items.length; index++) {
 const item = items[index]; doSomething(item);
 }
  85. @mathias for (const item of items) {
 doSomething(item);
 }

  86. @mathias items.forEach((item) => {
 doSomething(item);
 });

  87. @mathias Avoid holes! #ProTip Avoid out-of-bounds reads

  88. @mathias +0 === -0; // " true

  89. @mathias +0 === -0; // " true Object.is(+0, -0); //

    " false
  90. @mathias [3, 2, 1, +0];
 // PACKED_SMI_ELEMENTS


  91. @mathias [3, 2, 1, +0];
 // PACKED_SMI_ELEMENTS
 [3, 2, 1,

    -0];
 // PACKED_DOUBLE_ELEMENTS

  92. @mathias [3, 2, 1, +0];
 // PACKED_SMI_ELEMENTS
 [3, 2, 1,

    -0];
 // PACKED_DOUBLE_ELEMENTS
 [3, 2, 1, NaN, Infinity];
 // PACKED_DOUBLE_ELEMENTS
  93. @mathias Avoid holes! #ProTip Avoid elements kind transitions

  94. @mathias const arrayLike = {};
 arrayLike[0] = 'a';
 arrayLike[1] =

    'b';
 arrayLike[2] = 'c';
 arrayLike.length = 3;
  95. @mathias Array.prototype.forEach.call(arrayLike, (value, index) => {
 console.log(`${ index }: ${

    value }`);
 });
 // This logs '0: a', then '1: b', and finally '2: c'.
  96. @mathias const actualArray = Array.prototype.slice.call(arrayLike, 0);
 actualArray.forEach((value, index) => {


    console.log(`${ index }: ${ value }`);
 });
 // This logs '0: a', then '1: b', and finally '2: c'.
  97. @mathias const logArgs = function() {
 Array.prototype.forEach.call(arguments, (value, index) =>

    {
 console.log(`${ index }: ${ value }`);
 });
 };
 logArgs('a', 'b', 'c');
 // This logs '0: a', then '1: b', and finally '2: c'.
  98. @mathias const logArgs = (...args) => {
 args.forEach((value, index) =>

    {
 console.log(`${ index }: ${ value }`);
 });
 };
 logArgs('a', 'b', 'c');
 // This logs '0: a', then '1: b', and finally '2: c'.
  99. @mathias Avoid holes! #ProTip Prefer arrays over array-like objects

  100. @mathias $


  101. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8


  102. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax


  103. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.1.255 (candidate)
 d8>

  104. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.1.255 (candidate)
 d8>

    const array = [1, 2,, 3];
  105. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.1.255 (candidate)
 d8>

    const array = [1, 2,, 3]; %DebugPrint(array);

  106. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.1.255 (candidate)
 d8>

    const array = [1, 2,, 3]; %DebugPrint(array);
 DebugPrint: 0x313389e0e551: [JSArray]
 - map = 0x3133e0582889 [FastProperties]
 - prototype = 0x313360387f81
 - elements = 0x313389e0e4c9 <FixedArray[4]> [HOLEY_SMI_ELEMENTS (COW)]
 - length = 4
 - properties = 0x3133dae02241 <FixedArray[0]> {
 #length: 0x31336c242839 <AccessorInfo> (const accessor descriptor)
 }
 …
  107. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.1.255 (candidate)
 d8>

    const array = [1, 2,, 3]; %DebugPrint(array);
 DebugPrint: 0x313389e0e551: [JSArray]
 - map = 0x3133e0582889 [FastProperties]
 - prototype = 0x313360387f81
 - elements = 0x313389e0e4c9 <FixedArray[4]> [HOLEY_SMI_ELEMENTS (COW)]
 - length = 4
 - properties = 0x3133dae02241 <FixedArray[0]> {
 #length: 0x31336c242839 <AccessorInfo> (const accessor descriptor)
 }
 …
  108. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.1.255 (candidate)
 d8>

    const array = [1, 2,, 3]; %DebugPrint(array);
 DebugPrint: 0x313389e0e551: [JSArray]
 - map = 0x3133e0582889 [FastProperties]
 - prototype = 0x313360387f81
 - elements = 0x313389e0e4c9 <FixedArray[4]> [HOLEY_SMI_ELEMENTS (COW)]
 - length = 4
 - properties = 0x3133dae02241 <FixedArray[0]> {
 #length: 0x31336c242839 <AccessorInfo> (const accessor descriptor)
 }
 … 4
  109. None
  110. Avoid holes. — J.K. Rowling

  111. Avoid holes. Avoid out-of-bounds reads. — ancient Chinese proverb

  112. Avoid holes. Avoid out-of-bounds reads. Avoid elements kind transitions. —

    Justin Bieber
  113. Avoid holes. Avoid out-of-bounds reads. Avoid elements kind transitions. Prefer

    arrays over array-like objects. — Albert Einstein
  114. Avoid holes. Avoid out-of-bounds reads. Avoid elements kind transitions. Prefer

    arrays over array-like objects. Eat your vegetables. — this slide, just now
  115. @mathias One more thing…

  116. @mathias const array = [
 someValue,
 someOtherValue,
 theLastValue
 ];

  117. @mathias const array = [
 someValue,
 someOtherValue,
 /* more values

    */,
 theLastValue
 ];
  118. @mathias const array = new Array(9001);
 // …
 array[0] =

    someValue;
 array[1] = someOtherValue;
 // …
 array[9000] = theLastValue;
  119. @mathias const array = new Array(9001);
 // " an array

    with 9001 holes :'(
  120. @mathias new Array(n) + allows engines to preallocate space for

    n elements + optimizes array creation - creates a holey array - slower array operations (compared to packed arrays)
  121. @mathias const array = [];
 // …
 array.push(someValue);
 array.push(someOtherValue);
 //

    …
 array.push(theLastValue);
  122. @mathias + creates a packed array (never has any holes

    in it) + optimizes array operations - engines need to reallocate space as the array grows - slower array creation array = []; array.push(x);
  123. Use new Array(n) to optimize the creation of the array

    by pre-allocating the correct number of elements.
  124. Avoid new Array(n) to optimize operations on the array by

    avoiding holeyness.
  125. Write modern, idiomatic JavaScript, and let the JavaScript engine worry

    about making it fast.
  126. mths.be/v8ek

  127. Thank you! @mathias // @v8js mths.be/v8ek