我正在编写一个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中大致相等?
答案 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在转到hasOwnProperty
和in
的运行时之前探测到相同的缓存,那么在您的基准测试中,您会看到案例之间的等效性能。