为什么循环数组比 JavaScript 的原生 `indexOf` 快得多?

IT技术 javascript performance
2021-03-18 05:28:32

为什么遍历 Array 比 JavaScript 的 native 快得多indexOf是否有错误或我没有考虑的事情?我预计本机实现会更快。

                For Loop        While Loop      indexOf
Chrome 10.0     50,948,997      111,272,979     12,807,549
Firefox 3.6     9,308,421       62,184,430      2,089,243
Opera 11.10     11,756,258      49,118,462      2,335,347   

http://jsben.ch/#/xm2BV

6个回答

5 年后,浏览器发生了很多变化。现在, indexOf 性能有所提高,并且绝对优于任何其他自定义替代方案。

版本 49.0.2623.87(64 位)

Chrome 版本 49.0.2623.87(64 位)

@a3.14_Infinity 过时了可能不是无效的
2021-04-17 05:28:32
@agweber,这个测试在 indexOf 周围创建了一个“for 循环”,并将它与另一个包含一个 for 的其他 for 进行比较......我们在这里谈论的是 indexOf 本身与 for/while
2021-04-23 05:28:32
从而证明该问题无效。
2021-04-25 05:28:32
你能链接到你为此使用的测试吗?屏幕截图本身并没有显示实现,鉴于我从 < jsperf.com/thor-indexof-vs-for > 获得的结果,我得到了截然不同的结果。
2021-05-06 05:28:32
@pierre-maouni 你用什么工具来得到这样的结果?还是你自己设计的?
2021-05-15 05:28:32

好吧,看看这里的其他基准测试,我对大多数开发人员似乎进行基准测试的方式感到头疼。

抱歉,但这样做的方式会导致非常错误的结论,所以我必须稍微有点元,并对所提供的答案发表评论。

这里的其他基准有什么问题

测量在一个永远不会改变的数组中找到元素 777 的位置,总是导致索引 117 似乎很不合适,原因很明显,我很难解释为什么。你不能从这样一个过于具体的基准中合理地推断出任何东西!我能想到的唯一类比是对一个进行人类学研究,然后将调查结果称为对该人所居住国家的整个文化的概括概述。其他基准也好不到哪里去

更糟糕的是:接受的答案是一张没有链接到所用基准的图像,因此我们无法控制该基准的代码是否正确(我希望它是最初在 jsperf 链接中的屏幕截图)问题,后来编辑掉以支持新的 jsben.ch 链接)。它甚至不是对原始问题的解释:为什么一个比另一个表现更好(一开始是一个非常有争议的陈述)。

首先,您应该知道并非所有的基准测试站点都是平等的——由于它们自己的框架干扰了时间,有些站点可能会给某些类型的测量增加重大错误。

现在,我们应该比较在数组上进行线性搜索的不同方法的性能。考虑一下算法本身:

  • 查看数组中给定索引的值。
  • 将该值与另一个值进行比较。
    • 如果相等,返回索引
    • 如果不相等,则移动到下一个索引并比较下一个值。

这就是整个线性搜索算法,对吧?

所以一些链接的基准比较排序和未排序的数组(有时错误地标记为“随机”,尽管每次迭代的顺序相同 -相关 XKCD)。很明显,这不会以任何方式影响上述算法- 比较运算符不会看到所有值单调增加。

是的,在比较线性搜索二分插值搜索算法的性能时,有序数组与未排序数组很重要,但这里没有人这样做!

此外,显示的所有基准测试都使用一个固定长度的数组,其中有一个固定的索引。所有告诉您的是indexOf找到该确切长度的确切索引的速度- 如上所述,您无法从中概括出任何内容。

这是将问题中链接的基准或多或少复制到 perf.zone(比 jsben.ch 更可靠)的结果,但进行了以下修改:

  • 我们每次运行都选择数组的一个随机值,这意味着我们假设每个元素与其他元素一样有可能被选中
  • 我们针对 100 和 1000 个元素进行基准测试
  • 我们比较整数和短字符串。

https://run.perf.zone/view/for-vs-while-vs-indexof-100-integers-1516292563568

https://run.perf.zone/view/for-vs-while-vs-indexof-1000-integers-1516292665740

https://run.perf.zone/view/for-vs-while-vs-indexof-100-strings-1516297821385

https://run.perf.zone/view/for-vs-while-vs-indexof-1000-strings-1516293164213

这是我机器上的结果:

https://imgur.com/a/fBWD9

如您所见,结果会因基准测试和所使用的浏览器而发生巨大变化,并且每个选项至少在以下一种情况下获胜:缓存长度 vs 未缓存长度,while 循环 vs for 循环 vs indexOf.

所以这里没有统一的答案,而且随着浏览器和硬件的变化,这肯定会在未来发生变化。

你甚至应该对此进行基准测试吗?

应该注意的是,在开始构建基准之前,您应该确定线性搜索部分是否是一个瓶颈!它可能不是,如果是,更好的策略可能是使用不同的数据结构来存储和检索数据,和/或不同的算法。

那是不是说,这个问题是不相关的-它是罕见的,但它可以发生线性搜索性能事项; 我碰巧有一个例子:通过通过嵌套对象(使用字典查找)或嵌套数组(需要线性搜索)构造前缀树建立构造/搜索的速度

从这个 github 评论可以看出,基准测试涉及各种浏览器和平台上的各种现实和最佳/最坏情况的有效载荷。只有在经历了所有这些之后,我才能得出关于预期性能的结论。就我而言,对于大多数实际情况,通过数组的线性搜索比字典查找要快,但最坏情况下的性能更差到冻结脚本的程度(并且易于构建),因此实现被标记为一种“不安全”的方法,向其他人发出信号,告诉他们应该考虑使用代码的上下文。

Jon J 的回答也是退一步思考真正问题的一个很好的例子。

当您必须进行微基准测试时该怎么

所以让我们假设我们知道我们做了功课并确定我们需要优化我们的线性搜索。

那么重要的是我们期望找到元素的最终索引(如果有的话),正在搜索的数据类型,当然还有要支持的浏览器。

换句话说:找到任何指数的可能性是否相等(均匀分布),还是更有可能以中间为中心(正态分布)?会在开始还是接近结束时找到我们的数据?我们的值是保证在数组中,还是只在一定百分比的时间内?什么百分比?

我在搜索字符串数组吗?对象编号?如果它们是数字,它们是浮点值还是整数?我们是否试图针对旧智能手机、最新笔记本电脑或使用 IE10 的学校台式机进行优化?

这是另一件重要的事情:不要针对最佳情况进行优化,而是针对实际的最坏情况进行优化如果您正在构建一个 Web 应用程序,其中 10% 的客户使用非常旧的智能手机,请为此进行优化;他们的体验将是性能不佳的体验,而微优化则浪费在最新一代的旗舰手机上。

问问自己这些关于您应用线性搜索的数据的问题,以及您在其中进行搜索的上下文。然后制作适合这些标准的测试用例,并在代表您支持的目标的浏览器/硬件上测试它们。

可能是因为实际的 indexOf 实现所做的不仅仅是遍历数组。你可以在这里看到它的 Firefox 内部实现:

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf

出于理智的目的,有几件事可以减慢循环的速度:

  • 数组正在被重新转换为对象
  • fromIndex被转换为数值
  • 他们正在使用Math.max而不是三元
  • 他们正在使用 Math.abs
@Barmar - 足够公平,但是 polyfill 试图与原始实现非常相似,并且为了确定性能通常是一个不错的参考。
2021-04-17 05:28:32
发布在那里的代码是一个 polyfill,它不是实现中的实际代码。
2021-04-20 05:28:32
结果令人震惊......我的意思是甚至没有接近0_0。无论如何将数组转换为对象?说真的,它已经是一个对象了。那里的动机是什么?这似乎是一个边缘案例。
2021-05-01 05:28:32
仔细想想,while 循环成功地避免了需要Math.maxand Math.abs此外,您还可以通过一些技巧来添加fromIndex支持。
2021-05-01 05:28:32
@Barmar——这不是我要说的重点。无论特定机制如何,polyfill 都需要进行与内部实现相同的健全性检查。为此,它回答了“为什么本机速度较慢”的原始问题。
2021-05-01 05:28:32

indexOf 做了一堆 for 循环和 while 循环忽略的类型检查和验证。

这是 indexOf 算法:

https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/indexOf

编辑:我的猜测是 indexOf 对于大数组更快,因为它在循环之前缓存数组的长度。

是的,我也有同样的想法,但它看起来并没有更快jsperf.com/js-for-loop-vs-array-indexof/10 这甚至是一个不在数组中的值。
2021-05-01 05:28:32

使用我所做的编辑再运行一次测试。

我增加了数组的大小,并使您要搜索的索引也更大。似乎在大型数组中 indexOf 可能是一个更快的选择。

http://jsben.ch/#/xm2BV

编辑:基于更多的测试,在我使用的 Safari 版本 (5.0.3) 中,indexOf 似乎比 for 循环运行得更快,而在其他所有方面都慢。

for 循环在 chrome 中仍然更快。我很好奇你用 while 循环得到了什么结果。我在这里合并了两者:jsperf.com/js-for-loop-vs-array-indexof/10
2021-04-22 05:28:32