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

ssu*_*ube 25 javascript performance

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

在实际编写类之前,我认为查看最快的缓存检索方法是个好主意.想到的选项是:

1.基本财产访问:

if (x[k] !== undefined) {
    v = x[k];
}
Run Code Online (Sandbox Code Playgroud)

2.钥匙检查(自己):

if (x.hasOwnProperty(k)) {
    v = x[k];
}
Run Code Online (Sandbox Code Playgroud)

3.钥匙检查(一般):

if (k in x) {
    v = x[k];
}
Run Code Online (Sandbox Code Playgroud)

我假设3将是最快的(检查属性是否存在但不检索它或担心它存在的位置)并且1将是最慢的(实际获得属性,即使它没有做任何事情).

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

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

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

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

Vya*_*rov 32

x[k]Chrome(V8)性能背后的秘诀在于此大部分组装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
Run Code Online (Sandbox Code Playgroud)

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

| c:V8.KeyedLoadGenericLookupCache                               |    41999967 |
Run Code Online (Sandbox Code Playgroud)

这说明缓存确实被击中了.

现在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)
Run Code Online (Sandbox Code Playgroud)

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

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

如果V8在进入运行时之前探测相同的缓存hasOwnProperty,in然后在您的基准测试中,您会看到情况之间的等效性能.

  • 你应该写博客(回复你在删除的帖子中的评论) - 我对阅读非常感兴趣.关于引擎内部的信息量有点稀缺,我们都感兴趣:)你的网站也顺便说一下.(另外,如果有人感兴趣的话,这里是关于v8中对象表示的链接http://www.jayconrod.com/posts/52/a-tour-of-v8-object-representation) (4认同)