为什么array.push有时比array [n] = value更快?
作为测试一些代码的副作用,我编写了一个小函数来比较使用array.push方法与直接寻址(array [n] = value)的速度。 令我惊讶的是推送方法经常表现出更快,特别是在Firefox中,有时在Chrome中。 出于好奇:任何人都有解释吗? 你可以找到test @this页面(点击'Array methods comparison')
各种各样的因素发挥作用,大多数JS实现使用一个平面阵列,如果稍后有必要,它会转换为稀疏存储。
基本上,稀疏的决定是基于设置元素的启发式,以及为了保持平坦而浪费多少空间。
在你的情况下,你首先设置最后一个元素,这意味着JS引擎会看到一个数组,它需要长度为n
但只有一个元素。 如果n
足够大,这将立即使数组成为一个稀疏数组 - 在大多数引擎中,这意味着所有后续插入都将采用缓慢稀疏数组的情况。
您应该添加一个额外的测试,将数组从索引0填充到索引n-1 - 它应该快很多。
为了回应@Christoph和出于拖延的愿望,下面是关于如何实现数组(JS)的一个描述 - 特定于JS引擎的不同,从JS引擎到JS引擎不同,但总的原理是相同的。
所有JS Object
(不包括字符串,数字,true,false, undefined
或null
)都从基础对象类型继承而来 - 确切的实现有所不同,可能是C ++继承,或者是C语言中的手动操作以任何一种方式) - 基础对象类型定义了默认的属性访问方法,例如。
interface Object {
put(propertyName, value)
get(propertyName)
private:
map properties; // a map (tree, hash table, whatever) from propertyName to value
}
此Object类型处理所有标准属性访问逻辑,原型链等,然后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中创建一个数组时,引擎会创建类似于上述数据结构的东西。 将一个对象插入Array实例时,Array的put方法将检查属性名称是否为0到2 ^ 32之间的整数(或者可以转换为整数,例如“121”,“2341”等) -1(或可能2 ^ 31-1,我完全忘记)。 如果不是,则将put方法转发给基础对象实现,并完成标准[[Put]]逻辑。 否则,如果数据足够紧凑,则引擎将使用平面数组存储,在这种情况下,插入(和检索)只是标准数组索引操作,否则该值将放入数组自己的存储中,否则引擎将转换数组稀疏存储,并使/使用地图从propertyName获取值的位置。
我真的不确定在转换发生后,任何JS引擎目前是否从稀疏存储转换为平面存储。
Anyhoo,这是对发生的事情的一个相当高的概述,并且留下了一些更加棘手的细节,但这是一般的实现模式。 关于额外存储,以及put / get如何派发的具体细节因发动机而异 - 但这是我能够真正描述设计/实现的最清晰的部分。
一个小增加点,而ES规范将propertyName
称为字符串。JS引擎也倾向于专注于整数查找,所以如果您正在查看具有整数的对象, someObject[someInteger]
将不会将该整数转换为字符串属性例如。 数组,字符串和DOM类型( NodeList
等)。
这些是我在测试中获得的结果
在Safari上:
在FireFox上:
在IE7上:
根据你的测试 ,推送方法在IE7上似乎更好(差异巨大),并且由于在其他浏览器上的差异很小,它似乎是推送方法,真正是将元素添加到数组的最佳方式。
但是我创建了另一个简单的测试脚本来检查将数值附加到数组的快速方法,结果真的让我感到惊讶, 使用Array.length似乎比使用Array.push快得多 ,所以我真的不知道再说或想,我很无能。
顺便说一句:在我的IE7上,你的脚本会停下来,浏览器会问我是否要继续播放它(你知道典型的IE消息:“Stop runnign this script?...”)我会重新绘制以减少一些循环。
push()
是更一般[[Put]]的特例,因此可以进一步优化:
在数组对象上调用[[Put]]时,参数必须首先转换为无符号整数,因为所有属性名称(包括数组索引)都是字符串。 然后它必须与数组的长度属性进行比较,以确定是否需要增加长度。 推动时,不需要进行这种转换或比较:只需使用当前长度作为数组索引并增加它即可。
当然,还有其他一些会影响运行时的东西,例如调用push()
应该比通过[]
调用[[Put]]要慢,因为必须为原型链检查原型链。
正如olliej所指出的那样:实际的ECMAScript实现将优化转换,即对于数字属性名称,不会将字符串转换为uint,而只是进行简单的类型检查。 基本假设仍然适用,尽管其影响会比我原先假设的要少。
链接地址: http://www.djcxy.com/p/30429.html上一篇: Why is array.push sometimes faster than array[n] = value?