为什么比调用hasOwnProperty更快地获得成员?

时间:2014-02-13 19:21:25

标签: javascript performance

我正在编写一个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将是最慢的(实际获得属性,即使它没有做任何事情)。

Putting all of these into jsPerf产生了一些非常奇怪的结果。在Chrome(和Chromium)和IE中,#1的速度大约是其两倍。在Firefox中,#3有一个小优势,但三者之间的性能相似。如果我在VM中运行并不是没关系,并且版本之间没有太大的变化。

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

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

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

1 个答案:

答案 0 :(得分:31)

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

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

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

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

| c:V8.KeyedLoadGenericLookupCache                               |    41999967 |

说明缓存确实被击中了。

现在V8实际上并没有为x.hasOwnProperty(k)k in x使用相同的缓存,实际上它不使用任何缓存并且总是最终调用运行时,例如在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::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)

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

现在引擎之间的实现可能会有很大不同,所以不幸的是我无法完全解释其他引擎上发生的情况,但我的猜测是任何显示x[k]性能更快的引擎都采用类似的缓存(或者将x表示为字典,这也允许在生成的代码中快速探测)并且在案例之间显示相同性能的任何引擎要么不使用任何缓存,要么对所有三个操作使用相同的缓存(这将是做得很完美。)

如果V8在转到hasOwnPropertyin的运行时之前探测到相同的缓存,那么在您的基准测试中,您会看到案例之间的等效性能。