javascript - 为什么整数键的“映射”操作比 JavaScript (v8) 中的“对象”慢得多?
问题描述
我很高兴Map
在我的 JavaScript 代码库中到处使用索引访问,但我偶然发现了这个基准:https ://stackoverflow.com/a/54385459/365104
我也在这里重新创建了它:https ://jsben.ch/HOU3g
基准所做的基本上是用 1M 元素填充地图,然后迭代它们。
我希望 Map 和 Object 的结果可以相提并论,但它们有很大的不同 - 有利于 Object。
这是预期的行为吗?可以解释吗?是因为订购要求吗?还是因为 map 正在做一些关键散列?或者仅仅因为 Map 允许任何对象作为键(我希望它使用指针地址作为键,这不需要任何散列)?Map 和 Object 索引算法有什么区别?
这是非常出乎意料和令人沮丧的——基本上我将不得不恢复到老式的“对象即地图”编码风格。
更新#1
正如评论中所建议的,对象可能会优化为数组(因为它是按整数索引的,从零开始)。
将迭代顺序从size
to 0
-Object 更改仍然快 2 倍。使用字符串作为索引时,Map 的性能提高了 2 倍。
解决方案
(这里是 V8 开发人员。)
我将不得不恢复到老式的“对象即地图”编码风格。
如果你这样做,你将成为误导性微基准测试的受害者。
在使用连续整数作为键的非常特殊的情况下,普通的Object
会更快,是的。在这种情况下,没有什么比连续数组更好的了。因此,如果您提到的“代码库中任何地方的索引访问”确实使用了索引集,例如从 0 到 1M 的整数,那么使用 Object 或 Array 是一个好主意。但那是个特例。如果索引空间是稀疏的,事情看起来已经不同了。
在以随机顺序使用任意字符串的一般情况下, a 的Map
性能将明显优于 a Object
。更重要的是,处理此类对象属性访问的方式(在 V8 中,也很可能在其他引擎中)具有非局部影响:如果一个函数对对象属性查找处理系统的慢速路径施加过大压力,那么可能会减慢其他一些依赖于相同慢速路径进行属性访问的功能。
根本原因是引擎针对不同的使用模式优化了不同的东西。引擎可以在底层实现几乎相同的对象和地图;但这不是理想的行为,因为不同的使用模式受益于不同的内部表示和实现选择。所以引擎允许你向他们提供一个提示:如果你使用 a Map
,引擎会知道你打算使用这个东西作为地图(呃!),随机键会来来去去。如果您使用Object
,那么引擎将(至少一开始)假设您想要最适合您的平均对象的优化集,其中属性集相当小且静态。如果您使用Array
(或Object
只有整数属性,这在 JS 中几乎是一样的),那么你就可以让引擎轻松地为你提供快速的整数索引访问。
使用"x" + i
as key 是一个很好的建议,可以证明微基准测试可以多快被更改,从而产生相反的结果。但这里有一个剧透:如果您(仅)进行此修改,那么您测量的很大一部分将是数字到字符串的转换和字符串内部化,而不是 Map/Object 访问性能本身。
谨防微基准;他们具有误导性。您确实必须非常深入地分析它们(通过分析,和/或通过检查生成的代码,和/或通过跟踪其他引擎内部),以确保它们正在测量您认为他们正在测量的内容,从而产生结果这告诉你你认为他们在告诉你什么。
一般来说,强烈建议使用有代表性的测试用例进行性能测量。理想情况下,您的应用程序本身;或者通过将其中的真实部分提取到对真实数据进行操作的测试用例中。而且,如果您无法通过对整个生产应用程序的压力测试来衡量两个实现选择之间的差异,那么这不是值得担心的差异。使用微基准(即几条人工制作的线条),我可以“证明”几乎任何不适用于一般情况的东西。
推荐阅读
- python-3.x - 如何对已经分组的栏进行分组?
- windows - 我正在尝试查看此脚本的结果,当它运行时它会立即关闭并且不显示用户添加的组
- apache-kafka - 如何处理过期的生产者批次
- python - Sphinx 文档,自动模块的“缩短路径”
- javascript - 将字符串传递给 new Date() 构造函数会导致日期无效
- linux - 如何使用 Curl 将日志文件保存到 Mongodb 服务器
- java - 根据客户端 URL 启用 Spring Security 身份验证
- webp - 即使在实施不同的图像格式之后,Google PSI 也会发出“以下一代格式提供图像”警告
- r - 使用托管在闪亮服务器中的多个应用程序访问相同的数据
- python - 整个数据库是浮动的,它会导致报告问题