为数组赋值的性能

时间:2013-02-14 03:24:24

标签: javascript arrays optimization

代码优化在这里说的是,分析是优化javascript的第一步,建议的引擎是Chrome和Firefox的分析器。这些问题是他们以某种奇怪的方式告诉每个函数执行的时间,但我对它们没有任何好的理解。最有用的方法是分析器会告诉每行执行的次数,以及每行可能花费的时间。这样就可以严格地看到瓶颈。但在实施/找到此类工具之前,我们有两个选择:

1)制作自己的计算器,计算某个代码块或行执行的时间和次数 2)学会理解哪些是慢速方法,哪些不是

对于选项2 jsperf.com非常有帮助。我试图学习优化数组并在JSPERF.COM进行速度测试。下图显示了5个主要浏览器的结果,发现了一些我之前不知道的瓶颈。

Speed test

主要调查结果如下:

1)尽管使用哪种方法进行分配,但为数组分配值要比分配给正常变量要慢得多。

2)在性能关键循环之前预先初始化和/或预填充阵列可以显着提高速度

3)与将值推入数组(!)

相比,数学三角函数不是那么慢

以下是每项测试的解释:


1。 non_array(100%):

变量以这种方式给出了预定义的值:

var non_array_0=0;
var non_array_1=0;
var non_array_2=0;
...

在定时区域他们被称为这样:

non_array_0=0;
non_array_1=1;
non_array_2=2;
non_array_3=3;
non_array_4=4;
non_array_5=5;
non_array_6=6;
non_array_7=7;
non_array_8=8;
non_array_9=9;

以上是一个类似数组的变量,但似乎无法以其他方式迭代或引用这些变量作为对阵列的反对。或者有吗?

此测试中的任何内容都不比为变量指定数字更快。


2。 non_array_non_pre(83.78%)

与测试1完全相同,但变量未预先初始化或预先填充。速度是测试速度的83.78%。在每个测试的浏览器中,预填充变量的速度比未预先填充的速度快。 因此,在任何速度关键循环之外初始化(并可能预填充)变量。

测试代码在这里:

var non_array_non_pre_0=0;
var non_array_non_pre_1=0;
var non_array_non_pre_2=0;
var non_array_non_pre_3=0;
var non_array_non_pre_4=0;
var non_array_non_pre_5=0;
var non_array_non_pre_6=0;
var non_array_non_pre_7=0;
var non_array_non_pre_8=0;
var non_array_non_pre_9=0;

第3。 pre_filled_array(19.96%):

阵列是邪恶的!当我们丢弃正常变量(test1和test2)并将数组放入图片时,速度会显着下降。 虽然我们进行了所有优化(预初始化和预填充数组),然后直接分配值而没有循环或推送,速度降低到19.96%。这非常难过,我真的不明白为什么这样发生。这是我在这次测试中受到的主要冲击之一。数组是如此重要,我没有找到一种方法来制作没有数组的很多东西。

测试数据在这里:

pre_filled_array[0]=0;
pre_filled_array[1]=1;
pre_filled_array[2]=2;
pre_filled_array[3]=3;
pre_filled_array[4]=4;
pre_filled_array[5]=5;
pre_filled_array[6]=6;
pre_filled_array[7]=7;
pre_filled_array[8]=8;
pre_filled_array[9]=9;

4。 non_pre_filled_array(8.34%):

这与3的测试相同,但是数组成员未预先初始化或预先填充,只有优化是事先初始化数组:var non_pre_filled_array=[];

与预先测试的测试3相比,速度降低了58,23%。因此预先初始化和/或预填充阵列使速度加倍。

测试代码在这里:

non_pre_filled_array[0]=0;
non_pre_filled_array[1]=1;
non_pre_filled_array[2]=2;
non_pre_filled_array[3]=3;
non_pre_filled_array[4]=4;
non_pre_filled_array[5]=5;
non_pre_filled_array[6]=6;
non_pre_filled_array[7]=7;
non_pre_filled_array[8]=8;
non_pre_filled_array[9]=9;

5。 pre_filled_array [i](7.10%):

然后到循环。这个测试中最快的循环方法。阵列已预先初始化并预先填充。

与内联版本(测试3)相比,速度下降为64.44%。这是非常显着的差异,我想说,如果不需要,不要循环。如果数组大小很小(不知道它有多小,则必须单独测试),使用内联赋值而不是循环更明智。

因为速度下降是如此之大并且我们确实需要循环,所以找到better looping method(例如while(i--))是明智的。

测试代码在这里:

for(var i=0;i<10;i++)
{
  pre_filled_array[i]=i;
}

6。 non_pre_filled_array [i](5.26%):

如果我们不预先初始化和预填充阵列,速度会降低25,96%。同样,在速度关键循环之前预先初始化和/或预填充是明智的。

代码在这里:

for(var i=0;i<10;i++) 
{
  non_pre_filled_array[i]=i;
}

7。数学计算(1.17%):

每个测试都必须是一些参考点。数学函数被认为是缓慢的。测试包括10个&#34;重&#34;数学计算,但现在是在这个测试中让我印象深刻的另一件事。看看8和9的速度,我们将十个整数推入循环中的数组。计算这10个Math函数比将10个整数推入循环数组快30%以上。因此,将一些数组推送转换为预初始化的非数组并保留这些三角函数可能更容易。当然,如果每帧有数百或数千个计算,那么使用例如是明智的。 sqrt而不是sin / cos / tan并使用出租车距离进行距离比较和diamond angles (t-radians) for angle comparisons,但主要瓶颈仍然可能在其他地方:循环比内联慢,推动比使用直接分配预处理和/或预填充要慢,代码逻辑,绘图算法和DOM访问可能很慢。所有这些都无法在Javascript中进行优化(我们必须在屏幕上看到一些东西!)但是我们能做到的所有简单而重要的事情都是明智之举。 SO中的某个人说,代码是针对人类的,可读代码比快速代码更重要,因为维护成本是最大的成本。这是经济的观点,但我发现代码优化可以获得两者:优雅和可读性以及性能。如果实现5%的性能提升并且代码更直接,它会给人一种良好的感觉!

代码在这里:

non_array_0=Math.sqrt(10435.4557);
non_array_1=Math.atan2(12345,24869);
non_array_2=Math.sin(35.345262356547);
non_array_3=Math.cos(232.43575432);
non_array_4=Math.tan(325);
non_array_5=Math.asin(3459.35498534536);
non_array_6=Math.acos(3452.35);
non_array_7=Math.atan(34.346);
non_array_8=Math.pow(234,222);
non_array_9=9374.34524/342734.255;

8。 pre_filled_array.push(i)(0.8%):

推动是邪恶的!推动组合循环是恶毒的邪恶!这是由于某种原因将值分配给数组的非常慢的方法。测试5(循环中的直接赋值)比这种方法快近9倍,并且两种方法完全相同:将整数0-9分配给预初始化和预填充数组。我还没有测试过这种推送循环的恶意是由于推送或循环还是两者的组合或循环计数。在JSPERF.COM中有其他示例会产生相互矛盾的结果。用实际数据进行测试并做出决定是明智之举。此测试可能与使用的其他数据不兼容。

以下是代码:

for(var i=0;i<10;i++)
{
  pre_filled_array.push(i);
}

9。 non_pre_filled_array.push(i)(0.74%):

此测试中的最后和最慢的方法与测试8相同,但不预先填充数组。略慢于9,但差异不显着(7.23%)。但是,让我们举一个例子,将这种最慢的方法与最快的方法进行比较。 此方法的速度是方法1的速度的0.74%,这意味着方法1比这快135倍。因此,请仔细考虑,如果在特定用例中完全需要数组。如果它只有一次或几次推动,总速度差异不明显,但另一方面如果只有很少的推动,它们非常简单和优雅,可以转换为非数组变量。

这是代码:

for(var i=0;i<10;i++)
{
  non_pre_filled_array.push(i);
}

最后是强制性的SO问题:

因为根据此测试的速度差异在非数组变量赋值和数组赋值之间似乎是如此巨大,是否有任何方法可以获得非数组变量赋值的速度和数组的动态?

我不能在循环中使用var variable_$i = 1,以便将$ i转换为某个整数。我必须使用var variable[i] = 1,这比测试证明的var variable1 = 1要慢得多。只有存在大型阵列且在很多情况下它们才是最重要的。


编辑: 我做了一个新的测试,以确认数组访问的缓慢,并试图找到更快的方法:

http://jsperf.com/read-write-array-vs-variable

数组读取和/或数组写入比使用普通变量要慢得多。如果对数组成员执行了某些操作,则将数组成员值存储到临时变量更明智,将这些操作设置为temp变量,最后将值存储到数组成员中。虽然代码变得越来越大,但是使这些操作内联比使用循环要快得多。

结论:数组与普通变量类似于磁盘与内存。通常,内存访问比磁盘访问更快,而正常变量访问比数组访问更快。并且可能连接操作也比使用中间变量更快,但这使得代码有点不可读。


1 个答案:

答案 0 :(得分:4)

  

为数组赋值比分配给常规变量要慢得多。阵列是邪恶的!这非常难过,我真的不明白为什么会这样。数组非常重要!

这是因为正常变量是静态范围的,并且可以(并且)可以轻松优化。编译器/解释器将学习它们的类型,甚至可以避免重复分配相同的值。

这些优化也将针对数组进行,但它们并不那么容易,需要更长时间才能生效。解析属性引用时会有额外的开销,并且由于JavaScript数组是自动增长列表,因此也需要检查长度。

预先填充数组将有助于避免容量更改的重新分配,但对于您的小数组(length = 10),它应该没有太大的区别。

  

有没有什么方法可以获得非数组变量分配的速度和数组的动态?

没有。动力学确实有成本,但它们是值得的 - 就像循环一样。

你几乎不需要进行这样的微优化don't try it。在处理ImageData时,我唯一能想到的是固定大小的循环(n <= 4),内联是适用的。

  

推是邪恶的!

不,只有你的测试有缺陷。 jsperf片段在定时循环中执行,没有撕下和向下,只有你重置大小。您重复的push es已生成长度为十万的数组,并且需要内存(重新)分配。请访问http://jsperf.com/pre-filled-array/11处的控制台。

实际上push和财产分配一样快。良好的测量很少,但那些正确完成的测量显示不同浏览器引擎版本的不同结果 - 快速变化和意外。请参阅How to append something to an array?Why is array.push sometimes faster than array[n] = value?Is there a reason JavaScript developers don't use Array.push()? - 结论是您应该使用最适合您用例的内容,而不是您认为可能更快的内容。