为数组赋值的性能

Tim*_*nen 5 javascript arrays optimization

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

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

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

速度测试

主要调查结果如下:

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

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

3)与将数值推入数组相比,数学三角函数不是那么慢(!)

以下是每项测试的解释:


1. non_array(100%):

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

var non_array_0=0;
var non_array_1=0;
var non_array_2=0;
...
Run Code Online (Sandbox Code Playgroud)

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

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;
Run Code Online (Sandbox Code Playgroud)

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

此测试中的任何内容都不比为变量赋值更快.


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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

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;
Run Code Online (Sandbox Code Playgroud)

5. pre_filled_array [i](7.10%):

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

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

并且因为速度下降是如此巨大并且我们确实需要循环,所以找到更好的循环方法(例如while(i--))是明智的.

测试代码在这里:

for(var i=0;i<10;i++)
{
  pre_filled_array[i]=i;
}
Run Code Online (Sandbox Code Playgroud)

6. non_pre_filled_array [i](5.26%):

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

代码在这里:

for(var i=0;i<10;i++) 
{
  non_pre_filled_array[i]=i;
}
Run Code Online (Sandbox Code Playgroud)

7.数学计算(1.17%):

每个测试都必须是一些参考点.数学函数被认为是缓慢的.测试包括十次"重"数学计算,但现在又出现了另一件令我印象深刻的测试结果.看看8和9的速度,我们将十个整数推入循环中的数组.计算这10个Math函数比将10个整数推入循环数组快30%以上.因此,可能更容易将一些数组推送转换为预初始化的非数组并保留这些三角函数.当然,如果每帧有数百或数千个计算,那么使用例如是明智的.sqrt而不是sin/cos/tan并使用出租车距离进行距离比较和钻石角度(t弧度)进行角度比较,但主要瓶颈仍然是其他地方:循环比内联慢,推动比使用直接赋值慢预先启动和/或预填充,代码逻辑,绘图算法和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;
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

最后是强制性的SO问题:

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

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


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

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

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

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


Ber*_*rgi 4

给数组赋值比给普通变量赋值要慢得多。数组是邪恶的!这非常令人悲伤,我真的不明白为什么会发生这种情况。数组是如此重要!

这是因为普通变量的作用域是静态的,并且可以(而且)很容易进行优化。编译器/解释器将了解它们的类型,甚至可能避免重复分配相同的值。

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

预填充数组将有助于避免因容量变化而重新分配,但对于您的小数组(length= 10)来说应该没有太大区别。

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

不。动态确实有成本,但它们是值得的——就像循环一样。

您几乎不会遇到需要这种微观优化的情况,请不要尝试。我唯一能想到的是在处理 时固定大小的循环(n <= 4)ImageData,内联是适用的。

推动是邪恶的!

不,只是你的测试有缺陷。jsperf 片段在定时循环中执行,无需撕毁和 -down,并且只有在那里您才重置大小。您重复的pushes 已经生成了长度为十万的数组,相应地需要内存(重新)分配。请参阅http://jsperf.com/pre-filled-array/11上的控制台。

实际上push和属性分配一样快。良好的测量很少见,但那些正确完成的测量在不同的浏览器引擎版本中显示出不同的结果 - 变化迅速且出乎意料。请参阅如何向数组追加内容?,为什么 array.push 有时比 array[n] = value 快?JavaScript 开发人员不使用 Array.push() 是否有原因?- 结论是您应该使用最易读/最适合您的用例的内容,而不是您认为可能更快的内容。