DEV Community

Alireza Ebrahimkhani
Alireza Ebrahimkhani

Posted on

Be aware of Arrays - V8 engine advice

In this blog, I decided to talk about arrays and their behavior inside v8. By understanding these you can write efficient code that is good for v8 to optimize.

In V8(the JavaScript engine behind Google Chrome and Node.js), "elements kinds" refer to the internal classifications used to optimize array operations. V8 uses these classifications to make assumptions about the types of elements an array contains, which in turn allows it to optimize access to and manipulation of these arrays. Understanding elements kinds can be crucial for developers looking to write high-performance JavaScript code, as certain operations can cause an array to transition between kinds, potentially impacting performance.

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.

V8 categorizes arrays into different "elements kinds" based on the types of values they store and whether they have "holes" (missing elements). This classification allows V8 to use more efficient storage and access methods for arrays, depending on their content. Here's a closer look at each type:

Packed vs. Holey

Packed Elements: These arrays have no missing elements between the first and last defined positions. Access to packed arrays is typically faster because V8 can optimize memory layout and access patterns, assuming a continuous block of elements for example: const array = [1, 2, 3];

Holey Elements: These arrays contain holes, or undefined positions, which can occur if elements are deleted or if an array is declared with a larger initial size than the number of elements it contains. Operations on holey arrays are generally slower because V8 must check for the presence of elements before accessing them for example if you write code like the below sample v8 will treat this array like a Holey element:

Diagram

Types of Elements

Within the packed and holey classifications, arrays are further categorized by the types of elements they store:

Smi Elements: "Smi" stands for "small integer," referring to a specific optimization for storing 31-bit integers directly within pointers, saving space and access time. Arrays that exclusively contain Smi values are optimized differently from those containing other types of values.

Smi Elements

Double Elements: These arrays store floating-point numbers. Because floating-point numbers require a different storage format than integers or other types, V8 optimizes arrays that solely contain doubles differently.

Double elements

Elements: This category is for arrays that can contain elements of any type, including objects, strings, and symbols, in addition to numbers. These arrays are the most flexible in terms of the types of values they can contain but may not benefit from some of the optimizations that more specialized arrays do.

Elements

The elements kinds lattice

V8 implements this tag transitioning system as a lattice. Here’s a simplified visualization of that featuring only the most common elements kinds:

Element kinds lattice

It’s only possible to transition downwards through the lattice. Once a single floating-point number is added to an array of Smis, it is marked as DOUBLE, even if you later overwrite the float with a Smi. Similarly, once a hole is created in an array, it’s marked as holey forever, even when you fill it later.

In general, more specific elements kinds enable more fine-grained optimizations. The further down the elements kind is in the lattice, the slower manipulations of that object might be. For optimal performance, avoid needlessly transitioning to less specific types — stick to the most specific one that’s applicable to your situation.

Performance tips

In most cases, elements kind tracking works invisibly under the hood and you don’t need to worry about it. But here are a few things you can do to get the greatest possible benefit from the system.

  • Avoid reading beyond the length of the array

Nowadays, the performance of both for-of and forEach is on par with the old-fashioned for loop and when the collection you’re looping over is iterable just use for-of and for arrays specifically, you could use the forEach built-in.

  • Avoid elements kind transitions

In general, if you need to perform lots of operations on an array, try sticking to an elements kind that’s as specific as possible, so that V8 can optimize those operations as much as possible for example, just adding 1.1 to an array of small integers is enough to transition it to PACKED_DOUBLE_ELEMENTS.

Avoid elements kind transitions

The same thing goes for NaN and Infinity. They are represented as doubles, so adding a single NaN or Infinity to an array of SMI_ELEMENTS transitions it to DOUBLE_ELEMENTS.

  • Avoid polymorphism

If you have code that handles arrays of many different elements kinds, it can lead to polymorphic operations that are slower than a version of the code that only operates on a single elements kind.

Consider the following example, where a library function is called with various elements kinds. (Note that this is not the native Array.prototype.forEach)

Avoid polymorphism

Built-in methods (such as Array.prototype.forEach) can deal with this kind of polymorphism much more efficiently, so consider using them instead of userland library functions in performance-sensitive situations.

  • Avoid creating holes

This is one of the most important tips you have to consider because once the array is marked as holey, it remains holey forever — even if all its elements are present later!
Holey arrays occur when there are missing elements in the array, leading to less efficient access and manipulation due to the engine's need to handle potential "holes." Here’s how you can avoid creating holey arrays:

Creating holes

If you know the size of the array in advance but initialize it with empty slots, it becomes a holey array. Instead, pre-initialize it with known values. For integers, you might use 0 or another placeholder value. For a more concise way to initialize arrays without creating holes, use the fill method. This is especially useful when the array size is known, but you want to avoid holes.

Fill

Also deleting elements from an array can introduce holes, transforming it into a holey array. If you need to remove an element, consider setting it to undefined or null if you cannot use methods like splice to restructure the array without leaving gaps.

Splice

and for the last one when adding new elements to an array, use methods like push or unshift instead of directly setting them by index, which could create holes if the index is beyond the current array length.

Push-unshift

When writing performance-sensitive JavaScript code, understanding how these array types affect performance can guide how you structure your data. Keeping arrays homogenous (all Smi or all doubles) when possible can leverage V8's optimizations for faster access and manipulation.

Fun part :))

For debugging elements kinds to figure out a given object’s “elements kind”, get a debug build of v8 (either by building from source in debug mode or by grabbing a precompiled binary using jsvu), and run:
out/x64.debug/d8 --allow-natives-syntax
Note that “COW” stands for copy-on-write, which is yet another internal optimization. :))

debug

And, in the end hope you enjoy this blog and learn new things about v8 and its magics behind javascript.

Top comments (13)

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Why does every "500 javascript one-liners" article get inundated with attention but stuff like this just barely makes it into my feed on the front page. I want more interesting stuff like this.

Collapse
 
peerreynders profile image
peerreynders • Edited

Look in the right place and follow the right people.

This content was available 7 years ago by Mathias Bynens.

Mathias Bynens (@mathias@mastodon.xyz) - Mastodon

3 Posts, 232 Following, 2.34K Followers · I work on @ChromeDevTools (ex-@v8js) at Google and on ECMAScript through TC39. ♥ JavaScript, HTML, CSS, HTTP, performance, security, Bash, Unicode, i18n, macOS.

favicon mastodon.xyz

Axel Rauschmayer

Axel Rauschmayer (@rauschma@fosstodon.org) - Fosstodon

4.14K Posts, 219 Following, 4.69K Followers · Topics: #JavaScript #TypeScript #fedi22 Other interests: – Languages: German, English, French, Spanish, Dutch, Mandarin – Sustainability, degrowth, permaculture, urbanism – Tiny houses – Education – Psychology, getting out of one’s head, heart-centered living – Minimalist spirituality: Advaita, Daoism, Buddhism, Christian mysticism, J. Krishnamurti, … I live in Munich. http://pronoun.is/he Non-tech: – :pixelfed: Photos: @rauschma@pixelfed.de – 💬 Languages: @langtales@mastodon.social

favicon fosstodon.org

has covered holes since 2012:

If you are interested in this kind of thing: dig into why you may want to minimize megamorphic objects:

Collapse
 
gabrielfallen profile image
Alexander Chichigin

You might also want to check out old posts by one of the former V8 devs (nowadays a Dart dev) who developed some nice profiling tools: mrale.ph/

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

More importantly, how the hell was 2017 7 years ago that feels so wrong 😖

Collapse
 
alirezaebrahimkhani profile image
Alireza Ebrahimkhani

Thanks!
These articles are amazing. You can also follow the v8 engines' official blog at v8.dev

Collapse
 
manchicken profile image
Mike Stemle

“Holey” is probably better written as “sparse,” which is the common technical term for this phenomenon.

Collapse
 
miketalbot profile image
Mike Talbot ⭐

Fascinating, thank you!

Collapse
 
marco_cabrera_81e1796f41f profile image
marco cabrera

Wow. Great article I definitely don’t think a lot about the V8 side of things. Thanks for the great information.

Collapse
 
bwca profile image
Volodymyr Yepishev

Fascinating 🤓

Collapse
 
starswan profile image
Stephen Dicks

Its a shame to only frame this in terms of performance. Unlike other languages, Javascript only has one collection type called 'associative arrays' which handle both the 'array' (contiguous list of items) and hash/dictionary/map of other languages. x[9] = 3 where x is [1,2,3] is not just slow, it should be impossible on your domain as its effectively changing the type from list to dict - so ideally designs should hide these things to prevent clients misusing them.

Collapse
 
jdtoombs profile image
Jonny • Edited

Great article!

Quick question - if I had an array of ints and I were to remove one of the items and replace it with undefined, that would make it now PACKED_DOUBLE_ELEMENTS instead of PACKED_SMI_ELEMENTS?

The reason I ask is because I see if you add NaN or Infinity to a SMI_ELEMENTS array it becomes DOUBLE_ELEMENTS. Does v8 handle undefined the same way?

// example (remove 3 -> add undefined)
const array = [1, 2, undefined, 4]
Enter fullscreen mode Exit fullscreen mode
Collapse
 
s333mo profile image
Simone Pizzoleo

Crazy it only appeared me in 2024. Thanks for sharing, adding more and more knowledge everyday with these "kinds" (🤪) of articles!

Collapse
 
alihkz profile image
Alien

mind-blowing paper. thank you.