为什么array.push有时比array = value更快?

作为testing一些代码的副作用,我写了一个小函数来比较使用array.push方法与直接寻址(array [n] = value)的速度。 令我惊讶的是推送方法经常performance出更快,特别是在Firefox中,有时在Chrome中。 出于好奇:任何人都有一个解释呢? 你可以在这个页面findtest(点击'Array methods comparison')

各种各样的因素发挥作用,大多数JS实现使用平面arrays,如果稍后有必要,它会转换为稀疏存储。

基本上,稀疏的决定是基于哪些元素被设置的启发式,以及为了保持平坦将浪费多less空间。

在你的情况下,你最先设置的元素,这意味着JS引擎将看到一个数组,需要有一个长度为n但只有一个元素。 如果n足够大,这将立即使数组成为一个稀疏数组 – 在大多数引擎中,这意味着所有后续插入将采用慢稀疏数组的情况。

你应该添加一个额外的testing,在其中填充从索引0到索引n-1的数组 – 它应该快得多。

为了回应@Christoph和出于拖延的欲望,下面是关于如何实现JS(通常)在JS中实现的具体描述,从JS引擎到JS引擎不同,但总的原理是一样的。

所有的JS Object (从而不是string,数字,true,false, undefinednull )都是从基类对象typesinheritance而来的 – 确切的实现有所不同,可以是C ++inheritance,也可以用C语言手动实现以任何一种方式) – 基础对象types定义了默认的属性访问方法,例如。

 interface Object { put(propertyName, value) get(propertyName) private: map properties; // a map (tree, hash table, whatever) from propertyName to value } 

这个Objecttypes处理所有的标准属性访问逻辑,原型链等,然后Array实现变成

 interface Array : Object { override put(propertyName, value) override get(propertyName) private: map sparseStorage; // a map between integer indices and values value[] flatStorage; // basically a native array of values with a 1:1 // correspondance between JS index and storage index value length; // The `length` of the js array } 

现在当你在JS中创build一个数组时,引擎创build类似于上述数据结构的东西。 当您将一个对象插入Array实例时,Array的put方法将检查属性名称是否为0到2 ^ 32之间的整数(或者可以转换为整数,例如“121”,“2341”等) -1(或可能2 ^ 31-1,我忘了)。 如果不是,则将put方法转发给基类Object实现,并完成标准[[Put]]逻辑。 否则,如果数据足够紧凑,那么引擎将使用平面数组存储,在这种情况下插入(和检索)只是一个标准的数组索引操作,否则引擎会将数组转换为数组稀疏存储,并把/使用地图从propertyName获取值的位置。

我真的不知道,如果有任何JS引擎目前转换从稀疏到平面存储转换发生。

Anyhoo,这是一个相当高层次的概述发生了什么,并留下了一些更加棘手的细节,但这是一般的实施模式。 关于额外存储和如何put / get的具体细节不同于引擎和引擎 – 但这是我能够真正描述devise/实现的最清晰的。

一个小的附加点,而ES规范引用propertyName作为一个stringJS引擎也往往专注于整数查找,所以someObject[someInteger]不会将整数转换为一个string,如果你正在查看一个对象具有整数属性例如。 数组,string和DOMtypes( NodeList等)。

这些是我的testing结果

在Safari上:

  • Array.push(n)1,000,000个值:0.124秒
  • Array [n .. 0] =值(降序)1,000,000个值:3.697秒
  • 数组[0 .. n] =值(升序)1,000,000个值:0.073秒

在FireFox上:

  • Array.push(n)1,000,000个值:0.075秒
  • Array [n .. 0] =值(降序)1,000,000个值:1.193秒
  • 数组[0..n] =值(升序)1,000,000值:0.055秒

在IE7上:

  • Array.push(n)1,000,000个值:2.828秒
  • Array [n .. 0] =值(降序)1,000,000个值:1.141秒
  • 数组[0..n] =值(升序)1,000,000个值:7.984秒

根据你的testing推送方法在IE7上似乎更好(差别很大),而且在其他浏览器上的差异很小,所以推送方法真的是将元素添加到数组的最佳方式。

但是我创build了另一个简单的testing脚本来检查快速向数组中添加值的方法,结果真的让我感到惊讶, 使用Array.length似乎比使用Array.push快得多 ,所以我真的不知道是什么说再也不想想了,我是无能的。

顺便说一句:在我的IE7脚本停止和浏览器问我是否要让它继续(你知道典型的IE消息说:“停止运行这个脚本?…”)我会重新考虑,以减less一些循环。

push()是更一般的[[Put]]的特例,因此可以进一步优化:

在数组对象上调用[[Put]]时,必须首先将参数转换为无符号整数,因为所有属性名称(包括数组索引)都是string。 然后它必须与数组的长度属性进行比较,以确定是否需要增加长度。 推动时,不需要进行这种转换或比较:只要使用当前长度作为数组索引并增加它即可。

当然,还有其他一些会影响运行时的东西,例如调用push()应该比通过[]调用[[Put]]要慢,因为原型链必须被检查。


正如olliej所指出的那样:实际的ECMAScript实现将优化转换,即对于数字属性名称,不会将string转换为uint,而只是进行简单的types检查。 基本假设仍然有效,尽pipe它的影响会比我原先假设的要less。

这是一个很好的testing平台,它证实了直接分配比push快得多: http : //jsperf.com/array-direct-assignment-vs-push 。

编辑:显示累计结果数据似乎有一些问题,但希望它很快得到修复。

Push将其添加到最后,而array [n]必须通过数组才能find正确的位置。 可能取决于浏览器及其处理数组的方式。