How important is the order of properties in JavaScript objects?

In the case of the JavaScript engine, V8 is a very good one. In this article, I present the results of my small study of the effectiveness of one of the internal optimizations of V8.

V8 mechanism description


Before you continue reading, I advise you to read About the internal device of V8 and code optimization in order to better understand the essence of what is happening.

To make it very simple, V8 builds objects using internal hidden classes. Each such class corresponds to a unique object structure. For example, if we have a code like this:

 //    ,    C0
const obj = {}

//       "a",   1    0
obj.a = 1

//       "b",   2    1
obj.b = 2

//       "c",   3    2
obj.c = 3

The engine caches these class chains, and if you re-create the object with the same field names and the same order, it will not create hidden classes again, but will use existing ones. In this case, the property values ​​themselves may differ.

//     β€” C0
const obj2 = {} 

//    "a"    C0   β€” 1
obj2.a = 4

//    "b"    C1   β€” 2
obj2.b = 5

//    "c"    C2   β€” 3
obj2.c = 6

However, this behavior is easy to break. It is enough to define the same object, but with fields in a different order

//     β€” C0
const obj3 = {} 

//      "c"    C0.
//    β€” 4
obj3.c = 7

//      "a"    C4.
//    β€” 5
obj3.a = 8

//      "a"    C5. 
//    β€” 6
obj3.b = 9

Next, I tried to test and determine the difference in performance when working with objects in which the field names coincide and in which they differ.

Test method


I conducted three groups of tests:

  1. Static - property names and their order did not change.
  2. Mixed - only half of the properties changed.
  3. Dynamic - all properties and their order were unique for each object.

  • All tests were run on NodeJS version 13.7.0.
  • Each test was run in a separate, new thread.
  • All object keys have the same length, and all properties have the same value.
  • The Performance Timing API was used to measure runtime .
  • There may be slight fluctuations in the indicators. This is caused by different background processes running on the machine at that moment.
  • The code is as follows. I will give a link to the entire project below.

    const keys = getKeys();
    
    performance.mark('start');
    
    const obj = new createObject(keys);
    
    performance.mark('end');
    performance.measure(`${length}`, 'start', 'end');

results


Time to create one object


The first, most important and simple test: I took a cycle of 100 iterations. In each iteration, I created a new object and measured the time it took to create it for each iteration.
Iteration numberRuntime (mks)
StaticMixedDynamic
170,51674,51278,131
fifty6.247 -91.2%10,455 -85.9%41.792 -46.5%
1005.793 -91.7%9.845 -86.7%42,403 -45.7%



Timeline for creating one object depending on iteration

As you can see, during the first cold start in all groups, creating an object takes almost identical time. But already at the second or third iteration, the execution speed in the static and mixed groups (where V8 can apply optimization) compared with the dynamic group significantly increases.

And this leads to the fact that thanks to the internal optimization of V8, the 100th object from the static group is created 7 times faster.

Or, you could say it another way:
not following the order in which properties are declared in identical objects can slow down your code sevenfold .

The result does not depend on how exactly you create the object. I tried several different methods and they all gave almost identical values.

const obj = {}
for (let key of keys) obj[key] = 42

//      ,    -
const obj = new createObject(keys)

const obj = Object.fromEntries(entries)

const obj = eval('({ ... })')

Total time to create multiple objects


In the first test, we found that an object from the dynamic group can be created 30-40 microseconds longer. But this is only one object. And in real applications there can be hundreds or thousands of them. We calculate the total overhead costs at different scales. In the next test, I will sequentially repeat the first, but measure not the time to create one object, but the total time to create an array of such objects.

Array sizeLead time (ms)
StaticMixedDynamic
1000.751.29 +0.54 (+ 72.31%)4.75 + 4.00 (+ 536.35%)
10006.3412.06 +5.72 (+ 90.29%)39.11 +32.78 (+ 517.35%)
300016.4132.82 +16.42 (+ 100.07%)152.48 +136.08 (+ 829.42%)
10,00038.18101.84 +63.66 (+ 166.70%)428.29 +390.11 (+ 1021.63%)


Timeline for creating an array of objects depending on its size

As you can see, on the scale of the entire application, a simple internal optimization of V8 can accelerate tens of times.

Where is it important


Everywhere. In JavaScript, objects are everywhere. Here are a few life examples where maintaining a property order will give you a performance boost:

When working with Fetch

fetch(url1, {headers, method, body})
fetch(url2, {method, headers, body})

When working with jQuery

$.css({ color, margin })
$.css({ margin, color })

When working with Vue

Vue.component(name1, {template, data, computed})
Vue.component(name2, {data, computed, template})

When working with the Composite pattern (layout)

const createAlligator = () => ({...canEat(), ...canPoop()})
const createDog = () => ({...canPoop(), ...canEat()})

And in a great many other places.

Just following the order of properties in objects of the same type will make your code more productive.

References


Test code and detailed results
About V8 internals and code optimization

Source: https://habr.com/ru/post/undefined/


All Articles