V8 internals for JavaScript developers

V8 internals for JavaScript developers

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

August 24, 2018
Tweet

Transcript

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

  2. @mathias

  3. @mathias Elements kinds in V8

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


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

    PACKED_SMI_ELEMENTS

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

    PACKED_SMI_ELEMENTS
 array.push(4.56);

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

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

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

  9. @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
  10. @mathias Smi Doubles Regular elements Elements kinds

  11. @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
  12. @mathias array.length; // 5
 index 0 1 2 3 4

    value 1 2 3 4.56 'x'
  13. @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
  14. @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
  15. @mathias array[8];
 // " ??? index 0 1 2 3

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

    3 4 5 6 7 8 9 value 1 2 3 4.56 'x' 1
  17. @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
  18. @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
  19. @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
  20. @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
  21. @mathias array[8];
 // " ??? ❌
 8 >= 0 &&

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

    8 < array.length; // bounds check // " true ❌ hasOwnProperty(array, '8');
 // " false ❌ hasOwnProperty(Array.prototype, '8'); // " false ❌
  23. @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
  24. @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 ✅
  25. @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 ✅
  26. @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 ✅
  27. @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 ✅
  28. @mathias array[0];
 // " ???


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

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

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

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

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

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

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

  36. @mathias PACKED > HOLEY #$ %&

  37. @mathias Smi Doubles Regular elements Elements kinds

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

    Doubles, holey Regular elements, holey
  39. lattice

  40. @mathias PACKED_SMI_ELEMENTS HOLEY_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS HOLEY_DOUBLE_ELEMENTS PACKED_ELEMENTS HOLEY_ELEMENTS

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

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

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

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

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

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

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

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

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

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

    64
  51. @mathias

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

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


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

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

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

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

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

    'a';
 // HOLEY_ELEMENTS array[1] = 'b';
 index 0 1 2 value 'a' 'b'
  59. @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' ( now packed! (
  60. @mathias const array = new Array(3);
 // HOLEY_SMI_ELEMENTS
 array[0] =

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

    PACKED_ELEMENTS

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

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

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

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

    != null; i++) {
 doSomething(item);
 }
  66. @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);
 }
  67. @mathias for (const item of items) {
 doSomething(item);
 }

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

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

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

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

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


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

    -0];
 // PACKED_DOUBLE_ELEMENTS

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

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

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

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

    value }`);
 });
 // This logs '0: a', then '1: b', and finally '2: c'.
  78. @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'.
  79. @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'.
  80. @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'.
  81. @mathias Avoid holes! #ProTip Prefer arrays over array-like objects

  82. @mathias $


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


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


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

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

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

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

  88. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.0.244 (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)
 }
 …
  89. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.0.244 (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)
 }
 …
  90. @mathias $ rlwrap ~/projects/v8/out/x64.debug/d8 --allow-natives-syntax
 V8 version 7.0.244 (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)
 }
 … *
  91. None
  92. Avoid holes. — J.K. Rowling

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

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

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

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

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

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

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

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

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

    with 9001 holes :'(
  102. @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)
  103. @mathias const array = [];
 // …
 array.push(someValue);
 array.push(someOtherValue);
 //

    …
 array.push(theLastValue);
  104. @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);
  105. Use new Array(n) to optimize the creation of the array

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

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

    about making it fast.
  108. Thank you! @mathias // @v8js mths.be/v8ek