Skip to content

Instantly share code, notes, and snippets.

@mraleph
Last active October 26, 2022 15:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mraleph/49c69e4b90ee3590a957 to your computer and use it in GitHub Desktop.
Save mraleph/49c69e4b90ee3590a957 to your computer and use it in GitHub Desktop.

new Fn(...) vs Object.create(P)

Basics of object layout in V8

Each JavaScript object in V8 looks like this

+-------+
|  map  | -> pointer to the hidden class
+-------+
|       | -> pointer to the out-of-object properties storage
+-------+
|       | -> pointer to the array of elements
+-------+
|   1   | -\
+-------+  |
|   2   |  |
+-------+   > N slots reserved for in-object properties
~ ~ ~ ~ ~  |
+-------+  |
|   N   | -/
+-------+

There are several important things to note here:

  • Objects can have 0 in-object property slots use a dictionary for out-of-object property storage. This is a very generic and slow representation of a JavaScript object (aka dictionary mode). Fast mode JavaScript objects have 0 or more in-object properties slots and use an array for out-of-object property storage;
  • The more properties are stored in-object the better: it requires one less indirection to access them and also does not waste any memory on array header for out-of-object storage;
  • Once the object is allocated it is impossible to increase the amount of in-object slots for properties. If program continues to add properties to an object that has not free in-object slots then all newly added properties are going into the out-of-object storage which can be grown dynamically --- of course growing it dynamically also costs. That's why it is extremely important to have a good approximation of how properties an object is going to have in total;
  • Hidden class (aka map) completely describes layout of the object: how big this object is, which properties does it have and where (for fast mode objects), how many in-object property slots are already used etc. Hidden classes themselves are essentially immutable and everytime a new property is added to the object, this object has to switch to a new hidden class;

Example

Lets imagine what happens if V8 decides to give an object literal 1 in-object slot and we then add three properties to this object.

var obj = {};
obj.x = 0;
obj.y = 1;
obj.z = 2;

The evolution of the object in the heap will go as follows:

  1. Initially it will be empty and have an "empty" hidden class

     +-------+
     |  map  | -> #0 { /* empty hidden class */ }
     +-------+
     |       | -> [ /* empty array */ ]
     +-------+
     |       | -> [ /* empty array */ ]
     +-------+
     |       | <- one slot reserved for in-object properties
     +-------+  
    
  2. Once we add x the hidden class will change to a new one, which says that object contains property called in the first in-object slot and the value will be stored inside the slot

     +-------+
     |  map  | -> #1 { x: @in 0 }
     +-------+
     |       | -> [ /* empty array */ ]
     +-------+
     |       | -> [ /* empty array */ ]
     +-------+
     |   0   |
     +-------+  
    
  3. When we try to add y there will be no space inside the object left, so V8 will allocate an array for out-of-object properties and store y's value there. Hidden class will be changed once again to reflect this

     +-------+
     |  map  | -> #2 { x: @in 0, y: @out 0 }
     +-------+
     |       | -> [ 1 , _, _ ]
     +-------+
     |       | -> [ /* empty array */ ]
     +-------+
     |   0   |
     +-------+  
    

    Notice that V8 overallocated the out-of-object storage a bit, anticipating addition of more properties (growing the storage one property at a time would lead to quadratic complexity and produce garbage).

  4. When we try to add z there will be still no space inside the object, but enough space in the out-of-object storage so V8 will just use that.

     +-------+
     |  map  | -> #2 { x: @in 0, y: @out 0, z: @out 1 }
     +-------+
     |       | -> [ 1 , 2, _ ]
     +-------+
     |       | -> [ /* empty array */ ]
     +-------+
     |   0   |
     +-------+  
    

This example once again illustrates why it is very important to estimate the amount of in-object slots ahead of object allocation to achieve the most effecient representation after all properties are added.

Sidenote: if we execute the same code again, no new hidden classes will be allocate and we will arrive to the same hidden class in the end. This is a very important invariant and a lot of optimizations that happen in V8 are centered around it.

Estimating expected amount of properties.

The one and only heuristic that V8 currently uses to estimate expected amount of properties is based on counting assignments of form this.x = ... in the constructor function corresponding to the allocated object.

When we allocate the very first object through a constructor new Fn(...) V8 has to create an initial hidden class for this constructor: this will be the hidden class of an object at the very beginning of the constructor body, before execution of any statements within the constructor happens. When V8 allocates initial hidden class it has to decide how many slots for in-object properties to prereserve inside the object and as noted above it will not be able to give more to the object once it is allocated (more about it later).

To approximate a

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment