Element Kinds in V8

An arbitrary string can be used as a property name for a JavaScript object. But for some special subsets of names, it makes sense to do special optimizations in JavaScript engines. One such case is numeric array indices .



Although in most cases these properties behave indistinguishable from any other, the V8 engine, for optimization purposes, stores them separately from the rest and processes them in a special way. Inside V8 such properties are called elements ( elements ) of the object. Pretty logical: objects have properties that are accessible by name, and arrays have elements that are accessible by index.



Basic element grades



During JavaScript code execution, V8 keeps track of the sort of elements of each array - which elements it stores. This information allows the engine to better optimize array operations. For example, built-in functions like map, reduceor are forEachspecialized for each kind of element.



Consider, for example, an array like this:



const array = [1, 2, 3];


What elements does it contain? From the operator's point of view, typeofeverything is simple - they are type elements number. And this is all that can be said about them from within JavaScript: the language does not distinguish int, floatand double. However, there are differences at the engine level. The sort of elements of this array is PACKED_SMI _ELEMENTS. In V8 terms, SMI is a special format for storing small integers. Which means PACKED, we'll look at a bit later.



Adding a fractional number to an array makes its elements more general:



const array = [1, 2, 3];
//  : PACKED_SMI_ELEMENTS
array.push(4.56);
//  : PACKED_DOUBLE_ELEMENTS


Adding a line there makes the sort of elements even more general:



const array = [1, 2, 3];
//  : PACKED_SMI_ELEMENTS
array.push(4.56);
//  : PACKED_DOUBLE_ELEMENTS
array.push('x');
//  : PACKED_ELEMENTS


There are three main sorts of elements in this code:



  • SMI_ELEMENTS - for small integers
  • DOUBLE_ELEMENTS - for floating point numbers and integers too large for SMI
  • ELEMENTS β€” , SMI DOUBLE


, . , PACKED_ELEMENTS PACKED_DOUBLE_ELEMENTS.



:



  • V8 .
  • β€” , .
  • .


PACKED HOLEY



(dense), (packed), . "" ( , ) (sparse), "" (holey):



const array = [1, 2, 3, 4.56, 'x'];
//  : PACKED_ELEMENTS
array.length; // 5
array[9] = 1; //  array[5]  array[9]  
//  : HOLEY_ELEMENTS


V8 , . , , .



(SMI_ELEMENTS, DOUBLE_ELEMENTS ELEMENTS) (PACKED), (HOLEY), . , PACKED_SMI_ELEMENTS PACKED_DOUBLE_ELEMENTS, HOLEY_SMI_ELEMENTS.



:



  • (PACKED), (HOLEY).
  • , .
  • PACKED- HOLEY- ( ).




V8 . :



Grid of element grades



. , DOUBLE. , DOUBLE. , , - HOLEY, PACKED.



, V8 29 ( 22 β€” . .). .



, , . , . .





, . , V8 , .





, , . , , . , , array[42], array.length === 5. 42 , , . , V8 , , , .



, :



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


items[items.length], .



:



for (let index = 0; index < items.length; index++) {
  const item = items[index];
  doSomething(item);
}


, items β€” iterable- ( , ), for-of:



for (const item of items) {
  doSomething(item);
}


forEach:



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


, for-of forEach for.



, ! :



function maximum(array) {
  let max = 0;
  for (let i = 0; i <= array.length; i++) { //   
    if (array[i] > max) max = array[i];
  }
  return max;
}


array[array.length], , : , , V8 undefined. , .





, , , V8 .



, . , -0 PACKED_DOUBLE_ELEMENTS.



const array = [3, 2, 1, +0];
// PACKED_SMI_ELEMENTS
array.push(-0);
// PACKED_DOUBLE_ELEMENTS


, , .



-0, -0 +0 (, , ).



NaN Infinity. , , SMI_ELEMENTS DOUBLE_ELEMENTS.



const array = [3, 2, 1];
// PACKED_SMI_ELEMENTS
array.push(NaN, Infinity);
// PACKED_DOUBLE_ELEMENTS


, . , PACKED_SMI_ELEMENTS, .



, , . .



array-like objects



JavaScript, β€” , DOM API β€” , . " " (array-like) :



const arrayLike = {};
arrayLike[0] = 'a';
arrayLike[1] = 'b';
arrayLike[2] = 'c';
arrayLike.length = 3;


length. . :



Array.prototype.forEach.call(arrayLike, (value, index) => {
  console.log(`${ index }: ${ value }`);
});
// : '0: a', '1: b', '2: c'.


forEach , . , , array-like - , :



const actualArray = Array.prototype.slice.call(arrayLike, 0);
actualArray.forEach((value, index) => {
  console.log(`${ index }: ${ value }`);
});
// : '0: a', '1: b', '2: c'.


.



, arguments β€” . , , :



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


arguments rest parameters, , ECMAScript 2015. , .



function logArgs(...args) {
  args.forEach((value, index) => {
    console.log(`${ index }: ${ value }`);
  });
};
logArgs('a', 'b', 'c');
// : '0: a', '1: b', '2: c'.


arguments.



, array-like , .





, , , . :



const each = (array, callback) => {
  for (let index = 0; index < array.length; ++index) {
    const item = array[index];
    callback(item);
  }
};
const doSomething = (item) => console.log(item);

each([], () => {});

each(['a', 'b', 'c'], doSomething);
// `each`       `PACKED_ELEMENTS`.
//  V8   inline- (inline cache, IC),  `each`
//       . V8 
// ,   ,   `array.length`
//  `array[index]`  -      ,
//   ,      .   
//  `each`     .   
// `PACKED_ELEMENTS`,  V8   .  ,
//  .

each([1.1, 2.2, 3.3], doSomething);
// `each`       `PACKED_DOUBLE_ELEMENTS`.
// - ,   V8   `each`   ,
//  `array.length`  `array[index]`   .
//         
// ,      .

each([1, 2, 3], doSomething);
// `each`       `PACKED_SMI_ELEMENTS`.
//           `each`,
//     .


Array.prototype.forEach , , , .



, V8, .





. V8, . , :



const array = new Array(3);
//   ,    
// `HOLEY_SMI_ELEMENTS`,  ,   
//  

array[0] = 'a';
// ,    ,  !
//    `HOLEY_ELEMENTS`.

array[1] = 'b';
array[2] = 'c';
//      ,     
//     `HOLEY_ELEMENTS`   
// `PACKED_ELEMENTS`.


, , β€” !



:



const array = ['a', 'b', 'c'];
//  : PACKED_ELEMENTS


, , push.



const array = [];
// ...
array.push(someValue);
// ...
array.push(someOtherValue);




, , d8 ( jsvu). :



out/x64.debug/d8 --allow-natives-syntax


REPL d8, . %DebutPrint(object) ( elements):



d8> const array = [1, 2, 3]; %DebugPrint(array);
DebugPrint: 0x1fbbad30fd71: [JSArray]
 - map = 0x10a6f8a038b1 [FastProperties]
 - prototype = 0x1212bb687ec1
 - elements = 0x1fbbad30fd19 <FixedArray[3]> [PACKED_SMI_ELEMENTS (COW)]
 - length = 3
 - properties = 0x219eb0702241 <FixedArray[0]> {
    #length: 0x219eb0764ac9 <AccessorInfo> (const accessor descriptor)
 }
 - elements= 0x1fbbad30fd19 <FixedArray[3]> {
           0: 1
           1: 2
           2: 3
 }
[...]


--trace-elements-transitions. , V8 .



$ cat my-script.js
const array = [1, 2, 3];
array[3] = 4.56;

$ out/x64.debug/d8 --trace-elements-transitions my-script.js
elements transition [PACKED_SMI_ELEMENTS -> PACKED_DOUBLE_ELEMENTS] in ~+34 at x.js:2 for 0x1df87228c911 <JSArray[3]> from 0x1df87228c889 <FixedArray[3]> to 0x1df87228c941 <FixedDoubleArray[22]>



All Articles