当在不同的对象中使用相同的键时,在V8中调用缓慢的函数 [英] slow function call in V8 when using the same key for the functions in different objects

查看:138
本文介绍了当在不同的对象中使用相同的键时,在V8中调用缓慢的函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

也许不是因为呼叫很慢,而是查询是;我不确定,但这里有一个例子:

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

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

console.time('t');

for(var i = 0; i <100000000; i ++){
foo.fn();
}

console.timeEnd('t');

测试win8.1




  • firefox 35.01:〜240ms

  • chrome 40.0.2214.93(V8 3.30.33.15):〜760ms

  • msie 11: 34秒

  • nodejs 0.10.21(V8 3.14.5.9):〜100ms
    iojs 1.0.4(V8 4.1.0.12):〜760ms


现在这里是有趣的部分,如果我将 bar.fn 更改为 bar.somethingelse


  • chrome 40.0.2214.93(V8 3.30.33.15):〜100ms
  • nodejs 0.10.21(V8 3.14.5.9):〜100ms

  • Iojs 1.0.4(V8 4.1.0.12):〜 100毫秒


最近在v8中出错了吗?这是什么原因造成的?

解决方案

第一个基本原理。

em>隐藏类与转换相关联,以在松散的无形JavaScript对象中发现静态结构。

隐藏类描述该对象会将隐藏类链接到一起,以描述在某个对象上执行特定操作时应该使用哪个隐藏类。

例如,



例如下面的代码会导致下面的隐藏类链:

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


这个链是在你构造 o1 时创建的。当构建 o2 时,V8简单地遵循已建立的转换。



现在当属性 fn 用于存储一个函数V8试图给这个属性一个特殊的处理:而不是在隐藏的类中声明该对象包含一个属性 fn V8 将功能放入隐藏的课程中。

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



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

当评估 o2.fn = ... 赋值V8将会看到标记为 fn 的转换,但它会导致一个不适合的隐藏类:它包含 fff ggg 时,$ c>在 fn 属性中。注意:为了简单起见,我给出了函数名称 - V8不在内部使用它们的名称,而是使用它们的标识

由于V8无法关注这个转型V8将决定将其推广到隐藏类的决定是不正确和浪费的。图片将改变





V8将创建一个新的隐藏类,其中 fn 不再是一个简单的属性而不是一个常量函数属性。它将重新转换转换并标记旧的转换目标不推荐使用。请记住, o1 仍在使用它。然而,下次代码触及 o1 例如当从中加载属性时,运行时将会将 o1 从已弃用的隐藏类迁移出来。这样做是为了减少多态性 - 我们不希望 o1 o2 具有不同的隐藏类。



为什么在隐藏类上使用函数很重要?因为这给V8优化的编译器信息用于内联方法调用。如果调用目标存储在隐藏类本身上,它只能内联方法调用。



现在让我们将这些知识应用到上面的示例中。



由于转换之间发生冲突 bar.fn foo.fn 成为常规属性 - 函数直接存储在这些对象上,V8不能内联调用 foo.fn 导致性能降低。



以前是否可以内联电话? 即可。这是什么改变了:在旧版V8中没有折旧机制,所以即使在发生冲突并重新路由 fn 转换之后, foo 未被迁移到其中 fn 成为普通属性的隐藏类。相反, foo 仍然保留隐藏类,其中 fn 是一个常量函数属性,直接嵌入到隐藏类中,允许优化编译器内嵌它。



如果您在旧节点上尝试计时 bar.fn ,您会看到速度较慢: for(var i = 0; i <100000000; i ++){


} 

正是因为它使用隐藏类允许优化编译器内联 bar.fn 调用。



现在最后需要注意的是,此基准测试不是衡量函数调用的性能,而是衡量优化编译器是否可以通过在内部调用内部函数来将此循环减少为空循环。

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 = function() {};

console.time('t');

for (var i = 0; i < 100000000; i++) {
    foo.fn();
}

console.timeEnd('t');

Tested on win8.1

  • firefox 35.01: ~240ms
  • chrome 40.0.2214.93 (V8 3.30.33.15): ~760ms
  • msie 11: 34 sec
  • nodejs 0.10.21 (V8 3.14.5.9): ~100ms
  • iojs 1.0.4 (V8 4.1.0.12): ~760ms

Now here is the interesting part, if i change bar.fn to bar.somethingelse:

  • chrome 40.0.2214.93 (V8 3.30.33.15): ~100ms
  • nodejs 0.10.21 (V8 3.14.5.9): ~100ms
  • iojs 1.0.4 (V8 4.1.0.12): ~100ms

Something went wrong in v8 lately? What causes this?

解决方案

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;

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() { };

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

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.

这篇关于当在不同的对象中使用相同的键时,在V8中调用缓慢的函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆