为什么比调用hasOwnProperty更快地获得成员? [英] Why is getting a member faster than calling hasOwnProperty?

查看:130
本文介绍了为什么比调用hasOwnProperty更快地获得成员?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在编写一个JS对象,需要在字符串:函数对上执行真正基本的键值缓存。该类在客户端上运行并缓存部分编译的模板以呈现页面的一部分,因此它可能有20-200个项目。



在实际编写课程之前,我认为看看最快的缓存检索方法是个好主意。想到的选项包括:



1。基本属性访问:

  if(x [k]!== undefined){
v = x [K];
}

2。密钥检查(自己):

  if(x.hasOwnProperty(k)){
v = x [K];
}

3。密钥检查(一般):

  if(k in x){
v = x [k] ;
}

我认为3会最快(检查房产是否存在但是不检索它或担心它存在的位置)1会慢(实际得到属性,即使它没有做任何事情)。



将所有这些放入jsPerf 会产生一些非常奇怪的结果。在Chrome(和Chromium)和IE中,#1的速度大约是其两倍。在Firefox中,#3有一个小优势,但三者之间的性能相似。无论我是否在VM中运行都没关系,并且版本之间没有太大的变化。



我在解释这些结果时遇到了麻烦。可能#1注意到数据不会发生任何事情,所以只是在内部检查密钥,但为什么它比#3快?为什么#3没有得到相同的优化?



导致这些结果的原因是什么?是否有一些JIT优化我可能会对数据产生偏差?



更重要的是,为什么浏览器之间的差异如此之大,所有选项在FF中大致相等?

解决方案

x [k] 背后的秘密Chrome(V8)的性能在 ic-ia32.cc 。简而言之:V8维护一个全局缓存,它将一对(map,name)映射到索引,指定位置财产。 Map 是V8中用于隐藏类的内部名称其他JS引擎以不同方式调用它们(形状在SpiderMonkey和结构 >在JavaScriptCore中)。仅为快速模式对象的自身属性填充此缓存。快速模式是不使用字典来存储属性的对象的表示,而是更像是具有占据固定偏移的属性的C结构。



尽可能看一旦缓存在第一次执行循环时填充,它将始终在后续重复中被触发,这意味着属性查找将始终在生成的代码内部处理,并且永远不会进入运行时,因为所有属性基准测试实际上都在查找存在于对象上。如果您对代码进行了分析,您将看到以下行:

  256 31.8%31.8%KeyedLoadIC:快照中的键控加载IC 

并转储本机代码计数器会显示此信息(实际数量取决于重复基准测试的迭代次数) ):

  | c:V8.KeyedLoadGenericLookupCache | 41999967 | 

表明缓存确实被击中。



现在V8实际上并没有对x 中的 x.hasOwnProperty(k) k使用相同的缓存 ,实际上它不使用任何缓存并且总是最终调用运行时,例如在 hasOwnProperty 的配置文件中,你会看到很多C ++方法:

  339 17.0%17.0%_ZN2v88internal8JSObject28LocalLookupRealNamedPropertyEPNS0_4NameEPNS0_12LookupResultE.constprop.635 
254 12.7%12.7%v8 :: internal :: Runtime_HasLocalProperty(int,v8 :: internal :: Object **,v8 :: internal :: Isolate *)
156 7.8%7.8%v8 :: internal :: JSObject :: HasRealNamedProperty(v8 :: internal :: Handle< v8 :: internal :: JSObject>,v8 :: internal :: Handle< v8 :: internal: :名称>)
134 6.7%6.7%v8 :: internal :: Runtime_IsJSProxy(int,v8 :: internal :: Object **,v8 :: internal :: Isolate *)
71 3.6%3.6 %int v8 :: internal :: Search<(v8 :: internal :: SearchMode)1,v8 :: internal :: DescriptorArray>(v8 :: internal :: DescriptorArray *,v8 :: internal :: Name *,int)

这里的主要问题甚至不是这些是C ++方法而不是手写程序集(如KeyedLoadIC存根) )但这些方法正在执行相同的查找一次又一次没有缓存结果。



现在引擎之间的实现可能会大不相同,所以很遗憾我无法完全解释其他内容引擎,但我的猜测是任何显示更快 x [k] 性能的引擎都采用类似的缓存(或代表 x 作为字典,它还允许在生成的代码中快速探测),并且在案例之间显示相同性能的任何引擎要么不使用任何缓存,要么对所有三个操作使用相同的缓存(这将非常有意义)。 / p>

如果V8在进入 hasOwnProperty 的运行时之前探测到相同的缓存c $ c>然后在您的基准测试中,您会看到案例之间的等效性能。


I'm writing a JS object that needs to perform really basic key-value caching on string:function pairs. The class runs on the client and caches partially-compiled templates for rendering part of the page, so it may have anywhere from 20-200 items.

Before actually writing the class, I figured it would be a good idea to see what the fastest cache retrieval method was. The options that came to mind were:

1. Basic property access:

if (x[k] !== undefined) {
    v = x[k];
}

2. Key Check (Own):

if (x.hasOwnProperty(k)) {
    v = x[k];
}

3. Key Check (General):

if (k in x) {
    v = x[k];
}

I assumed that 3 would be fastest (checks to see if the property exists but doesn't retrieve it or worry about where it exists) and 1 would be slowest (actually gets the property, even if it doesn't do anything).

Putting all of these into jsPerf yielded some very strange results. In both Chrome (and Chromium) and IE, #1 is about twice as fast. In Firefox, #3 has a minor edge, but performance is similar between all three. It didn't matter if I was running in a VM or not, and didn't change a lot between versions.

I'm having trouble explaining these results. It might be that #1 notices that nothing will happen to the data and so just checks for the key internally, but why is it faster than #3? Why does #3 not get the same optimization?

What is causing these results? Is there some JIT optimization I might be hitting that skews the data?

More importantly, why is this so drastically different between browsers, with all options being roughly equal in FF?

解决方案

The secret behind x[k] performance on Chrome (V8) is in this chunk of assembly from ic-ia32.cc. In short: V8 maintains a global cache that maps a pair of (map, name) to an index specifying location of the property. Map is an internal name used in V8 for hidden classes other JS engines call them differently (shapes in SpiderMonkey and structures in JavaScriptCore). This cache is populated only for own properties of fast mode objects. Fast mode is the representation of an object that does not use dictionary to store properties, but instead is more like a C-structure with properties occupying fixed offsets.

As you can see once the cache is populated the fist time your loop is executed, it will always be hit on the subsequent repetitions, meaning that the property lookup will always be handled inside the generated code and will never enter runtime because all properties benchmark is looking up actually exist on the object. If you profile the code you will see the following line:

256   31.8%   31.8%  KeyedLoadIC: A keyed load IC from the snapshot

and dumping native code counters would show this (actual number depends on the number of iterations you repeat the benchmark):

| c:V8.KeyedLoadGenericLookupCache                               |    41999967 |

which illustrates that cache is indeed being hit.

Now V8 does not actually use the same cache for either x.hasOwnProperty(k) or k in x, in fact it does not use any cache and always end up calling runtime, e.g. in the profile for hasOwnProperty case you will see a lot of C++ methods:

339   17.0%   17.0%  _ZN2v88internal8JSObject28LocalLookupRealNamedPropertyEPNS0_4NameEPNS0_12LookupResultE.constprop.635
254   12.7%   12.7%  v8::internal::Runtime_HasLocalProperty(int, v8::internal::Object**, v8::internal::Isolate*)
156    7.8%    7.8%  v8::internal::JSObject::HasRealNamedProperty(v8::internal::Handle<v8::internal::JSObject>, v8::internal::Handle<v8::internal::Name>)
134    6.7%    6.7%  v8::internal::Runtime_IsJSProxy(int, v8::internal::Object**, v8::internal::Isolate*)
 71    3.6%    3.6%  int v8::internal::Search<(v8::internal::SearchMode)1, v8::internal::DescriptorArray>(v8::internal::DescriptorArray*, v8::internal::Name*, int)

and the main problem here is not even that these are C++ methods and not handwritten assembly (like KeyedLoadIC stub) but that these methods are performing the same lookup again and again without caching the outcome.

Now the implementations can be wildly different between engines, so unfortunately I can't give complete explanation of what happens on other engines, but my guess would be that any engine that shows faster x[k] performance is employing similar cache (or represents x as a dictionary, which would also allow fast probing in the generated code) and any engine that shows equivalent performance between cases either does not use any caching or employs that same cache for all three operations (which would make perfect sense).

If V8 probed the same cache before going to runtime for hasOwnProperty and in then on your benchmark you would have seen equivalent performance between cases.

这篇关于为什么比调用hasOwnProperty更快地获得成员?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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