Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

V8: Heterogeneous Array Literals

Tags:

javascript

v8

I'm getting lost in the weeds with V8 source as well as articles on the subject and I came across a blog post which stated:

If you are forced to fill up an array with heterogeneous elements, let V8 know early on by using an array literal especially with fixed-size small arrays.

let array = [77, 88, 0.5, true]; //V8 knows to not allocate multiple times.

If this is true, then why is it true? Why an array literal? What's so special about that vs creating an array via a constructor? Being new to the V8 source, it's difficult to track-down where the difference in homogeneous/heterogeneous arrays lie.

Also, if an answerer can point me towards the relevant V8 source, that'd be appreciated.

EDIT: slight clarification on my question (array literal vs. array constructor)

like image 843
Matt Avatar asked Jun 20 '18 03:06

Matt


Video Answer


1 Answers

From this blog post provided by Mathias, a V8 developer:

Common elements kinds

While running JavaScript code, V8 keeps track of what kind of elements each array contains. This information allows V8 to optimize any operations on the array specifically for this type of element. For example, when you call reduce, map, or forEach on an array, V8 can optimize those operations based on what kind of elements the array contains.

Take this array, for example:

const array = [1, 2, 3];

What kinds of elements does it contain? If you’d ask the typeof operator, it would tell you the array contains numbers. At the language-level, that’s all you get: JavaScript doesn’t distinguish between integers, floats, and doubles — they’re all just numbers. However, at the engine level, we can make more precise distinctions. The elements kind for this array is PACKED_SMI_ELEMENTS. In V8, the term Smi refers to the particular format used to store small integers. (We’ll get to the PACKED part in a minute.)

Later adding a floating-point number to the same array transitions it to a more generic elements kind:

const array = [1, 2, 3];
// elements kind: PACKED_SMI_ELEMENTS
array.push(4.56);
// elements kind: PACKED_DOUBLE_ELEMENTS
Adding a string literal to the array changes its elements kind once again.

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

....

V8 assigns an elements kind to each array. The elements kind of an array is not set in stone — it can change at runtime. In the earlier example, we transitioned from PACKED_SMI_ELEMENTS to PACKED_ELEMENTS. Elements kind transitions can only go from specific kinds to more general kinds.

THUS, behind the scenes, if you're constantly adding different types of data to the array at run time, the V8 engine has to adjust behind the scenes, losing the default optimization.

As far as constructor vs. array literal

If you don’t know all the values ahead of time, create an array using the array literal, and later push the values to it:

const arr = [];
arr.push(10);

This approach ensures that the array never transitions to a holey elements kind. As a result, V8 can optimize any future operations on the array more efficiently.

Also, to clarify what is meant by holey,

Creating holes in the array (i.e. making the array sparse) downgrades the elements kind to its “holey” variant. Once the array is marked as holey, it’s holey forever — even if it’s packed later!

It might also be worth mentioning that V8 currently has 21 different element kinds.

More resources

  • V8 Internals for JavaScript Developers - a talk by Mathias Bynens
  • JavaScript Engines - How Do They Even? - a talk by Franziska Hinkelmann
like image 188
Derek Pollard Avatar answered Oct 03 '22 20:10

Derek Pollard