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

时间:2011-07-13 17:20:31

标签: javascript performance

为什么循环数组的速度比JavaScript的原生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

7 个答案:

答案 0 :(得分:78)

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

Version 49.0.2623.87 (64-bit)

Chrome版本49.0.2623.87(64位)

答案 1 :(得分:12)

可能是因为实际的indexOf实现不仅仅是循环遍历数组。您可以在此处查看Firefox的内部实现:

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

为了理智起见,有几件事可以减慢循环:

  • 正在将数组重新强制转换为Object
  • fromIndex正在转换为数字
  • 他们使用Math.max而不是三元
  • 他们正在使用Math.abs

答案 2 :(得分:11)

好的,看看其他基准测试,我对大多数开发人员的基准测试方式感到头疼。

道歉,但是它的完成方式会导致可怕的错误结论,因此我必须稍微考虑并对所提供的答案进行评论。

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

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

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

首先,你应该知道not all benchmarking sites are created equal - 由于他们自己的框架干扰了时间安排,有些可能会给某些类型的测量增加重大错误。

现在,我们假设要比较在阵列上进行线性搜索的不同方式的性能。想想算法本身一秒钟:

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

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

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

是的,在比较线性搜索二进制 interpolation search 算法的性能时,有序vs未排序的数组可能很重要,但这里没有人这样做!

此外,所有显示的基准测试都使用固定长度的数组,其中包含固定的索引。所有这些告诉你的是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

正如您所看到的,结果会根据基准和正在使用的浏览器彻底改变 ,并且每个选项在至少一个场景中胜出:缓存长度与未缓存长度,而loop vs for-loop vs indexOf

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

您是否应该对此进行基准测试?

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

这并不是说这个问题无关紧要 - 很少见,但可以发生线性搜索性能很重要;我碰巧有一个例子:建立构建/搜索通过嵌套对象构建的prefix trie(使用字典查找)或嵌套数组(需要线性搜索)的速度。

As can be seen this github comment,基准测试涉及各种浏览器和平台上的各种现实和最佳/最坏情况的有效负载。只有在经历了所有这些之后,我才能得出关于预期绩效的结论。在我的情况下,对于大多数现实情况,通过数组的线性搜索比字典查找更快,但最坏情况的性能更糟糕到冻结脚本(并且易于构造),因此实现被标记为"不安全"向别人发出信号的方法,他们应该考虑使用代码的上下文。

Jon J's answer也是回过头来思考真正问题的一个很好的例子。

必须进行微观基准

时该怎么办

因此,让我们知道我们做了功课,并确定我们需要优化线性搜索。

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

换句话说:是否可以找到任何指数(均匀分布),还是更有可能以中间(正态分布)为中心?会在开始还是接近结束时找到我们的数据?我们的价值是保证在阵列中,还是只保留一定比例的时间?百分比是多少?

我在搜索一串字符串吗?对象数字?如果它们是数字,它们是浮点值还是整数?我们是否正在尝试针对旧版智能手机,最新笔记本电脑或使用IE10的学校桌面进行优化?

这是另一个重要的事情:不要针对最佳案例性能进行优化,针对现实最坏情况性能进行优化。如果您正在构建一个网络应用程序,其中10%的客户使用非常旧的智能手机,请优化;他们的经验将是令人无法忍受的糟糕表现,而微优化则浪费在最新一代的旗舰手机上。

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

答案 3 :(得分:5)

indexOf执行一系列类型检查和验证,for循环和while循环忽略。

这是indexOf算法:

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

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

答案 4 :(得分:2)

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

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

http://jsben.ch/#/xm2BV

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

答案 5 :(得分:1)

值得注意的是,如果您要做的就是保留项目列表并检查是否存在(例如,避免向数组中添加重复的ID),使用反映的键保持OBJECT要快得多每个ID。如果您认为我错了,请将以下内容与数组+ indexOf进行比较。对于对象方法我们说的是181ms,对于数组indexOf方法我们说的是1 MINUTE。

var objs = []
var i_uid = {} // method 1
var a_uid = [] // method 2
var total_count = 100000, idLen = 5
var ts, te, cObj = 0

// method 1
ts = new Date()
while (cObj < total_count) {
    var u = uid(idLen),
        o = {
            uid: u,
            text: 'something',
            created: new Date()
        }
    if (!i_uid[u]) { // ensure unique uids only
        objs.push(o)
        i_uid[u] = cObj // current array position as placeholder
        cObj++
    }
    else {
        console.log('unique violation [duplicate uid', u, ']')
    }
}
te = new Date()
console.log('loaded ' + total_count + ' with object method in', (te - ts), 'ms')

i_uid = {} // free-up
cObj = 0 // reset
objs = [] // reset

// method 2
ts = new Date()
while (cObj < total_count) {
    var u = uid(idLen),
        o = {
            uid: u,
            text: 'something',
            created: new Date()
        }
    if (a_uid.indexOf(u) == -1) { // ensure unique uids only
        objs.push(o)
        a_uid.push(u)
        cObj++
    }
    else {
        console.log('unique violation [duplicate uid', u, ']')
    }
}
te = new Date()
console.log('loaded ' + total_count + ' with array + indexOf method in', (te - ts), 'ms')

function uid(l) {
    var t = '',
        p = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789',
        pl = p.length
    for (var i = 0; i < l; i++)
        t += p.charAt(Math.floor(Math.random() * pl))
    return t
}

答案 6 :(得分:1)

我们每次都可以信任一个简单的循环。

我写下面的代码来发现这个问题的答案。它也可运行。 它表明,在考虑性能时,普通for loop是最佳解决方案。

(代码也可以在jsfiddle上找到)

&#13;
&#13;
console.clear()

let a = []
// populating array data
for (let i = 0; i < 100000; i++) {
	a.push(i)
}

let testNum = 90000
let found
let totalMS4ForLoop = 0
let totalMS4IndexOf = 0
let start
let end
// simulating 10000 requests which are come consecutively
for (o = 0; o < 10000; o++) {

  start = Date.now()
  for (let i = 0; i < a.length; i++) {
    if (a[i] == testNum) { found = a[i]; break }
  }
  end = Date.now()
  totalMS4ForLoop += end - start

  start = Date.now()
  found = a[a.indexOf(testNum)]
  end = Date.now()
  totalMS4IndexOf += end - start

}

console.log("10000 for-loop executions took total " + totalMS4ForLoop + " ms.")
console.log("10000 indexOf executions took total " + totalMS4IndexOf + " ms.")
&#13;
&#13;
&#13;