slow function call in V8 when using the same key for the functions in different objects

后端 未结 2 829
Happy的楠姐
Happy的楠姐 2021-01-30 06:34

Maybe not because the call is slow, but rather the lookup is; I\'m not sure, but here is an example:

var foo = {};
foo.fn = function() {};

var bar = {};
bar.fn          


        
2条回答
  •  自闭症患者
    2021-01-30 07:11

    First fundamentals.

    V8 uses hidden classes connected with transitions to discover static structure in the fluffy shapeless JavaScript objects.

    Hidden classes describe the structure of the object, transitions link hidden classes together describing which hidden class should be used if a certain action is performed on an object.

    For example the code below would lead to the following chain of hidden classes:

    var o1 = {};
    o1.x = 0;
    o1.y = 1;
    var o2 = {};
    o2.x = 0;
    o2.y = 0;
    

    enter image description here

    This chain is created as you construct o1. When o2 is constructed V8 simply follows established transitions.

    Now when a property fn is used to store a function V8 tries to give this property a special treatment: instead of just declaring in the hidden class that object contains a property fn V8 puts function into the hidden class.

    var o = {};
    o.fn = function fff() { };
    

    enter image description here

    Now there is an interesting consequence here: if you store different functions into the field with the same name V8 can no longer simply follow the transitions because the value of the function property does not match expected value:

    var o1 = {};
    o1.fn = function fff() { };
    var o2 = {};
    o2.fn = function ggg() { };
    

    When evaluating o2.fn = ... assignment V8 will see that there is a transition labeled fn but it leads to a hidden class that does not suitable: it contains fff in fn property, while we are trying to store ggg. Note: I have given function names only for simplicity - V8 does not internally use their names but their identity.

    Because V8 is unable to follow this transition V8 will decide that its decision to promote function to the hidden class was incorrect and wasteful. The picture will change

    enter image description here

    V8 will create a new hidden class where fn is just a simple property and not a constant function property anymore. It will reroute the transition and also mark old transition target deprecated. Remember that o1 is still using it. However next time code touches o1 e.g. when a property is loaded from it - runtime will migrate o1 off the deprecated hidden class. This is done to reduce polymorphism - we don't want o1 and o2 to have different hidden classes.

    Why is it important to have functions on the hidden classes? Because this gives V8's optimizing compiler information it uses to inline method calls. It can only inline method call if call target is stored on the hidden class itself.

    Now lets apply this knowledge to the example above.

    Because there is a clash between transitions bar.fn and foo.fn become normal properties - with functions stored directly on those objects and V8 can't inline the call of foo.fn leading to a slower performance.

    Could it inline the call before? Yes. Here is what changed: in older V8 there was no deprecation mechanism so even after we had a clash and rerouted fn transition, foo was not migrated to the hidden class where fn becomes a normal property. Instead foo still kept the hidden class where fn is a constant function property directly embedded into the hidden class allowing optimizing compiler to inline it.

    If you try timing bar.fn on the older node you will see that it is slower:

    for (var i = 0; i < 100000000; i++) {
        bar.fn();  // can't inline here
    }       
    

    precisely because it uses hidden class that does not allow optimizing compiler to inline bar.fn call.

    Now the last thing to notice here is that this benchmark does not measure the performance of a function call, but rather it measures if optimizing compiler can reduce this loop to an empty loop by inlining the call inside it.

提交回复
热议问题