Why is array.push sometimes faster than array[n] = value?
作为测试一些代码的附带结果,我编写了一个小函数来比较使用array.push方法与直接寻址(array[n]=value)的速度。令我惊讶的是,推送方法经常表现得更快,特别是在火狐和Chrome中。只是出于好奇:有人对此有解释吗?您可以在本页找到测试(单击"数组方法比较")。
各种各样的因素都发挥了作用,大多数JS实现使用一个平面数组,如果以后有必要,它将转换为稀疏存储。
基本上,稀疏化的决定是一个启发式的,基于什么元素被设置,以及为了保持平坦而浪费了多少空间。
在您的例子中,您首先设置最后一个元素,这意味着JS引擎将看到一个数组,该数组的长度需要为
您应该添加一个额外的测试,在这个测试中,您将数组从索引0填充到索引N-1——它应该快得多,快得多。
为了响应@christoph,出于拖延的愿望,这里描述了如何(通常)在JS中实现数组——具体细节因JS引擎而异,但一般原理是相同的。
所有的JS EDCOX1,2,s(所以不串,数字,真,假,EDOCX1,3,或EDOCX1,4)从基类对象类型继承——确切的实现是不同的,它可以是C++继承,或者是在C中手工操作(无论用哪种方式都有好处)——基对象类型定义默认值属性访问方法,例如
1 2 3 4 5 6 | interface Object { put(propertyName, value) get(propertyName) private: map properties; // a map (tree, hash table, whatever) from propertyName to value } |
此对象类型处理所有标准属性访问逻辑、原型链等。然后数组实现变成
1 2 3 4 5 6 7 8 9 | 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中创建数组时,引擎将创建类似于上述数据结构的内容。在数组实例中插入对象时,数组的Put方法会检查属性名是否是0到2^32-1(或者可能是2^31-1,我完全忘记)之间的整数(或者可以转换为整数,例如"121"、"2341"等)。如果不是,则将put方法转发到基本对象实现,并完成标准的[[put]]逻辑。否则,将该值放入数组自己的存储中,如果数据足够紧凑,则引擎将使用平面数组存储,在这种情况下,插入(和检索)只是一个标准的数组索引操作,否则引擎将把数组转换为稀疏存储,并将put/get使用映射从propertyname获取值位置。
老实说,我不确定在发生转换之后,是否有任何JS引擎当前从稀疏存储转换为平面存储。
总之,这是对所发生的事情的一个相当高级别的概述,并且遗漏了一些更棘手的细节,但这是一般的实现模式。附加存储和Put/Get如何发送的细节在不同的引擎之间是不同的——但这是最清楚的,我可以真正描述设计/实现。
一个小的附加点,而es规范引用
这些是我通过你的测试得到的结果
狩猎之旅:
- array.push(n)1000000值:0.124秒
- 数组…0=值(降序)1000000个值:3.697秒
- 数组[ 0…n]=值(升序)1000000值:0.073秒
火狐:
- array.push(n)1000000值:0.075秒
- 数组…0]=值(降序)1000000值:1.193秒
- 数组[ 0…n]=值(升序)1000000值:0.055秒
论基督论:
- array.push(n)1000000值:2.828秒
- 数组…0]=值(降序)1000000值:1.141秒
- 数组[ 0…n]=值(升序)1000000值:7.984秒
根据您的测试,push方法在IE7上似乎更好(差异很大),而且由于在其他浏览器上差异很小,所以它似乎是向数组添加元素的最佳方法。
但是我创建了另一个简单的测试脚本来检查什么方法可以快速地将值附加到数组中,结果让我很惊讶,与使用array.push相比,使用array.length似乎要快得多,所以我真的不知道该说什么或该怎么想了,我一无所知。
顺便问一句:在我的IE7上,你的脚本停止运行,浏览器问我是否要继续运行(你知道典型的IE消息说:"停止运行这个脚本吗?"……")我会建议减少一点环。
在数组对象上调用[[Put]]时,必须首先将参数转换为无符号整数,因为所有属性名(包括数组索引)都是字符串。然后,必须将其与数组的长度属性进行比较,以确定是否需要增加长度。推送时,不需要进行这样的转换或比较:只需使用当前长度作为数组索引并增加它。
当然,还有其他一些事情会影响运行时,例如,通过
正如Olliej指出的那样:实际的EcmaScript实现将优化转换,即对于数字属性名,不执行从字符串到uint的转换,只执行简单的类型检查。基本假设应该仍然成立,尽管它的影响将比我最初假设的要小。
这里有一个很好的测试台,它证实了直接分配比push快得多:http://jspef.com/array-direct-assignment-vs-push。
编辑:显示累积结果数据似乎有些问题,但希望很快就能解决。
push将其添加到末尾,而数组[n]必须通过数组找到正确的位置。可能取决于浏览器及其处理数组的方式。