对于 VS Foreach 的阵列性能(在 AS3/Flex 中)

Posted

技术标签:

【中文标题】对于 VS Foreach 的阵列性能(在 AS3/Flex 中)【英文标题】:For VS Foreach on Array performance (in AS3/Flex) 【发布时间】:2010-11-03 21:29:49 【问题描述】:

哪个更快?为什么?

var messages:Array = [.....]

// 1 - for
var len:int = messages.length;
for (var i:int = 0; i < len; i++) 
    var o:Object = messages[i];
    // ...


// 2 - foreach
for each (var o:Object in messages) 
    // ...

【问题讨论】:

您可以使用某种计时器轻松进行基准测试? 嗯,是的。但我很想知道这些东西是如何在 Flash Player 中实现的。可能有些会产生更多的垃圾(例如迭代器对象)或者它们是等效的 - 而“foreach”只是“for”的合成糖? 我认为您太早接受了答案。我发现与 back2dos 的结果相反。 对于 oshyshko:因为 each 不仅仅是语法糖,它会将迭代器设置为集合值而不是索引。但是,我认为实际代码中循环类型之间的任何速度优势都可能与难以预测的内部类型检查或类型转换之类的东西相形见绌。我怀疑,一般情况的结果是否适用于您的真实代码,这是任何人的猜测。 【参考方案1】:

对于数组来说,for 会更快……但根据情况,它可能是最好的 foreach……请参阅.net benchmark test。

就我个人而言,我会使用其中任何一个,直到我需要优化代码为止。过早的优化是浪费:-)

【讨论】:

他问的是 as3,而不是 .net 框架。不同的语言执行代码的方式不同 他仍然是正确的,但是在 AS3 中,for 循环比 for each 循环快很多。这是因为 for 循环是直接引用。 @Unreality 是的,当我发布我的答案时,我知道他要求的是 as3 而不是 .net,但我感觉到了基准测试(我找不到专门针对 as3 的测试)可以很好地反映任何 for/foreach 循环的一般性能。 for each 在 AS3 中比 for 更快 - 如果你愿意,可以给它一个基准。 Tyler:我不同意,从快速测试来看,他的 for each 循环似乎更快,因为它不包含任何变量赋值。请参阅我的答案中的示例代码。【参考方案2】:

很抱歉证明你们错了,但每个人都更快。甚至很多。除非,如果您不想访问数组值,但是 a) 这没有意义,b) 这里不是这种情况。

因此,我在我的超级新博客上做了详细的post...:D

问候

back2dos

【讨论】:

证明谁错了?这个网站不是要证明人们是错的,而是要为人们提供由同行投票选出的正确答案。如果我的回答没有帮助,则不会被赞成。我对此没有任何问题。但是,关于您的答案,如果您提供的证据比您自己的博客文章更多,那就太好了……否则,它似乎与编辑***文章对您有利;-) 我不买这些结果。与 for each 中的增量相比,您正在 for 循环中进行变量赋值。要比较循环,您还应该在 for each 循环中进行赋值,如果这样做,结果会相反。 (顺便说一句,与单个变量赋值相比,循环样式之间的性能差异很小,因此非常微不足道。) + 1. 我认为你是对的,尽管有些人似乎不同意(虽然没有读过你的博客)。【参考方案3】:

从我坐的位置来看,在最小情况下,常规的 for 循环比 for each 循环要快一些。此外,与 AS2 天一样,通过 for 循环递减通常会带来非常小的改进。

但实际上,这里的任何细微差别都会与您在循环中实际执行的操作的要求相形见绌。您可以找到在任何一种情况下工作得更快或更慢的操作。真正的答案是,任何一种循环都不能有意义地说比另一种更快 - 您必须分析您的代码,因为它出现在您的应用程序中。

示例代码:

var size:Number = 10000000;
var arr:Array = [];
for (var i:int=0; i<size; i++)  arr[i] = i; 
var time:Number, o:Object;

// for()
time = getTimer();
for (i=0; i<size; i++)  arr[i]; 
trace("for test: "+(getTimer()-time)+"ms");

// for() reversed
time = getTimer();
for (i=size-1; i>=0; i--)  arr[i]; 
trace("for reversed test: "+(getTimer()-time)+"ms");

// for..in
time = getTimer();
for each(o in arr)  o; 
trace("for each test: "+(getTimer()-time)+"ms");

结果:

for test: 124ms
for reversed test: 110ms
for each test: 261ms

编辑:为了改进比较,我更改了内部循环,因此它们除了访问集合值之外什么都不做。

编辑 2:对 oshyshko 评论的回答:

    编译器可以跳过我的内部循环中的访问,但它不能。如果是这样,循环退出的速度会快两到三倍。 您发布的示例代码中的结果发生了变化,因为在该版本中,for 循环现在具有隐式类型转换。为了避免这种情况,我将作业排除在循环之外。 当然,有人可能会争辩说,在for 循环中添加一个额外的演员是可以的,因为“真正的代码”无论如何都需要它,但对我来说,这只是另一种说法,“没有一般的答案;哪个循环更快取决于你在你的循环中做了什么”。这是我给你的答案。 ;)

【讨论】:

@fenomas arr[i] 可能会被解释器跳过,因为结果会被忽略。还要使值类型严格:“o:Object”->“o:Number”。试试这个: 1) var time:Number, o:Number, v:Number 2) 替换 "arr[i]" -> "v = arr[i]" 3) // for..in time = getTimer();对于每个(arr 中的 o) v = o; trace("对于每个测试:"+(getTimer()-time)+"ms");我使用 Player 10 的结果: [trace] for test: 895ms [trace] for reversed test: 565ms [trace] for each test: 750ms BTW:你怎么看,为什么反向更好?是因为“i>=0”可能比“i oshyshko,看我的编辑。对于为什么递减更快,我认为这是因为 + 具有内部类型检查,因为它可以应用于字符串和数字,并且 ++ 继承了它。但是考虑到它在 1000 万次迭代中只增加了几毫秒,我什至不应该提到它。人们可能最好不知道这种事情。 ;) fenomas:我认为通过删除项目访问权限,您错过了重点。使用 foreach 您不必在 Actionscript 中进行分配(这会更慢),但您可以访问 Array 中的每个项目(并且以键入的方式)。使用 for 循环,您必须手动执行此操作。 OP 询问了 Arrays 的循环性能,我认为如果你遍历一个 Array,你这样做是为了访问它包含的元素。所以,我绝对认为 for 循环中的分配应该在那里。 Juan:我没有删除项目访问权限;我的示例中的所有循环都包含一个访问权限。我删除了一个变量赋值,这在某些循环中可能是必要的,而在其他循环中是不必要的。 fenomas:很公平,你是对的;访问并不一定意味着分配。例如,我认为您将变量键入为 Object 而不是 Number 或 int 会有所不同。【参考方案4】:

在遍历数组时,for each 循环在我的测试中要快得多。

var len:int = 1000000;
var i:int = 0;
var arr:Array = [];

while(i < len) 
    arr[i] = i;
    i++;


function forEachLoop():void 
    var t:Number = getTimer();
    var sum:Number = 0;
    for each(var num:Number in arr) 
        sum += num;
    
    trace("forEachLoop :", (getTimer() - t));


function whileLoop():void 
    var t:Number = getTimer();
    var sum:Number = 0;
    var i:int = 0;
    while(i < len) 
        sum += arr[i] as Number;                
        i++;
    
    trace("whileLoop :", (getTimer() - t));


forEachLoop();
whileLoop();

这给出了:

forEachLoop : 87 而循环:967

在这里,可能大部分 while 循环时间都用于将数组项转换为数字。不过,我认为这是一个公平的比较,因为这就是你在 for each 循环中得到的结果。

我的猜测是,这种差异与上述事实有关,as 运算符相对昂贵且数组访问也相对较慢。使用 for each 循环,我认为这两种操作都可以在本地处理,而不是在 Actionscript 中执行。

但是请注意,如果确实发生了类型转换,则 for each 版本会慢得多,而 while 版本会明显更快(尽管仍然是 for each beat while):

为了测试,将数组初始化更改为:

while(i < len) 
    arr[i] = i + "";
    i++;

现在结果是:

forEachLoop : 328 而循环:366

forEachLoop : 324 而循环:369

【讨论】:

呃,这段代码没有比较哪种循环更快;每个循环内部所做的事情的性能显然使循环本身的风格之间的差异相形见绌。当然,哪个循环更快取决于你在其中做什么。此外,将您的 while() 循环更改为 for() 循环,它会大大加快速度。不知道为什么,大概是内部优化。 关于 while/for 循环,大约一年前,我在 flashcoders 的列表中发布了两个循环的解密字节码,显示几乎没有区别。 (如果你愿意,我可以重新发布它们)。更重要的是,基准没有显着差异。所以,我怀疑使用 for 循环而不是 while 循环会有什么不同。无论如何,我将代码更改为使用 for 循环,甚至去掉了“as”运算符。尽管如此,每个版本的 for 循环需要 57 毫秒,而 for 循环需要 679 毫秒。我同意大部分时间都花在循环体上。然而,在其他条件相同的情况下,每个都运行得更快。 Juan,我同意在某些情况下 for..each 更快。也有不是的情况。我要说的是两件事:首先,在最小的情况下,for 循环更快,所以如果可以说任何一种排序“本质上”更快,那就是 for 循环。其次,在非最小情况下,哪个循环更快取决于循环的主体。因此,没有普遍的案例答案。 啊,还有两个笔记。首先,我绝对收回我所说的 for() 比 while() 快,那是我的错误。其次,如果您仍然认为这里的示例代码是一个很好的一般情况,请尝试删除“as”运算符,然后将循环中的“+=”运算符更改为“-=”运算符。 while 循环现在将大大加快,这意味着您的结果由 + 运算符的内部类型检查决定,而不是循环本身的性质。 嗯,可以。没有检查 -= (我确实检查了删除“as”)。无论如何,我同意在某些情况下,如您的示例所示,for 循环可能会更快。更重要的是,正如我认为我们都同意的那样,大多数“现实世界”循环的瓶颈将是它的主体,而不是循环机制。在大多数实际情况下,您不会迭代超过 10000000 个项目。我倾向于几乎完全使用while(或for)循环,但是当我意识到在大多数情况下我测试的每个循环都没有明显变慢时(并且在许多情况下更快),并且更具可读性和简洁性(至少对我而言) ,我切换到每个。【参考方案5】:

我之前和几位同事讨论过这个问题,我们都发现了针对不同场景的不同结果。但是,为了比较,我发现有一个测试很有说服力:

var array:Array=new Array();
for (var k:uint=0; k<1000000; k++) 
    array.push(Math.random());


stage.addEventListener("mouseDown",foreachloop);
stage.addEventListener("mouseUp",forloop);

/////// Array /////

/* 49ms */
function foreachloop(e) 
    var t1:uint=getTimer();
    var tmp:Number=0;
    var i:uint=0;
    for each (var n:Number in array) 
        i++;
        tmp+=n;
    
    trace("foreach", i, tmp, getTimer() - t1);

/***** 81ms  ****/
function forloop(e) 
    var t1:uint=getTimer();
    var tmp:Number=0;
    var l:uint=array.length;
    for(var i:uint = 0; i < l; i++)
        tmp += Number(array[i]);
    trace("for", i, tmp, getTimer() - t1);

我喜欢这个测试的地方在于,您在两个循环的每次迭代中都有一个键和值的参考(在“for-each”循环中删除键计数器并不重要)。此外,它与 Number 一起运行,这可能是您想要优化的最常见的循环。最重要的是,获胜者是“for-each”,这是我最喜欢的循环:P

注意事项:

-在“for-each”循环的函数中引用局部变量中的数组是无关紧要的,但在“for”循环中,您确实会遇到减速带(75ms 而不是 105ms):

function forloop(e) 
    var t1:uint=getTimer();
    var tmp:Number=0;
    var a:Array=array;
    var l:uint=a.length;
    for(var i:uint = 0; i < l; i++)
        tmp += Number(a[i]);
    trace("for", i, tmp, getTimer() - t1);

-如果你用 Vector 类运行相同的测试,结果会有点混乱:S

【讨论】:

与 Juan 的回答一样,值得注意的是,如果您删除 Number() 强制转换并对值求和(使用 -= 而不是 +=),for 循环会更快地出现。当然,我理解加入 Number() 演员表背后的原因,因为你可以使用 for..each 免费获得它,但是我想不出这样的情况,代码在演员表中的工作方式与没有演员表的情况不同...【参考方案6】:

只是一个附加组件:

for each...in 循环并不能保证 array/vector 以它们存储在其中的顺序进行枚举。 (XML 除外) 这是一个至关重要的区别,IMO。

"...因此,您不应该编写依赖于 for- each-in 或 for-in 循环的枚举顺序,除非您正在处理 XML 数据..." C.Moock

(我希望不要因为这句话而触犯法律……)

基准测试愉快。

【讨论】:

能否提供一个循环未按您期望的顺序运行的示例?【参考方案7】:

也许在所有元素都存在且从零开始(0 到 X)的数组中,使用 for 循环会更快。在所有其他情况下(稀疏数组),每个使用它可能会快很多。 原因是数组中使用了两种数据结构:Hast table 和 Debse Array。 请阅读我使用 Tamarin 源的阵列分析: http://jpauclair.wordpress.com/2009/12/02/tamarin-part-i-as3-array/

for 循环将检查未定义的索引,其中 for each 将跳过那些跳转到 HastTable 中的下一个元素的索引

【讨论】:

【参考方案8】:

伙计们! 尤其是胡安·巴勃罗·卡利法诺。 我检查了你的测试。获取数组项的主要区别。 如果你输入var len : int = 40000;,你会看到'while'循环更快。 但它会因大量数组而失败,而不是 for..each。

【讨论】:

以上是关于对于 VS Foreach 的阵列性能(在 AS3/Flex 中)的主要内容,如果未能解决你的问题,请参考以下文章

ActionScript 3 AS3在阵列中?

AS3 - 相对于角度移动阵列对象

闪存 AS3 的动态阵列

ActionScript 3 AS3阵列差异

foreach + break vs linq FirstOrDefault 性能差异

AS3阵列差异