我很好奇for..in
出于性能原因应该优先考虑.each
。
答案 0 :(得分:5)
For .. in
是标准语言流控制的一部分。
相反,each
会调用一个闭包,因此需要额外的开销。
.each {...}
是与方法调用.each({...})
此外,由于它是一个闭包,在each
代码块内部,您不能使用break
和continue
语句来控制循环。
http://kunaldabir.blogspot.it/2011/07/groovy-performance-iterating-with.html
更新了基准Java 1.8.0_45 Groovy 2.4.3:
这是另一个100000次迭代的基准:
lines = (1..100000)
// with list.each {}
start = System.nanoTime()
lines.each { line->
line++;
}
println System.nanoTime() - start
// with loop over list
start = System.nanoTime()
for (line in lines){
line++;
}
println System.nanoTime() - start
结果:
答案 1 :(得分:4)
让我们从动态完成调用以及使用Java逻辑更直接地完成调用(我将调用那些静态调用)的角度来理解事物。
在for-in
的情况下,Groovy在Iterator上运行,为了得到它,我们对iterator()进行了一次动态调用。如果我没有弄错,则使用普通的Java方法调用逻辑完成hasNext和next调用。因此,对于每次迭代,我们这里只有2个静态调用。根据基准测试,必须注意的是,第一次调用iterator()会导致严重的初始化时间,因为这可能会启动元类系统并且需要一些时间。
在each
的情况下,我们对每个自身进行动态调用,以及为open块创建对象(Closure的实例)。然后每个(Closure)也将调用iterator(),但是未缓存...以及所有一次性成本。在循环期间,hasNext和next使用Java逻辑完成,该逻辑进行2次静态调用。对Closure实例的调用是使用方法调用的java标准逻辑完成的,然后使用动态调用调用doCall。
总结一下,每次迭代for-in
仅使用2次静态调用,而each
则有3次静态调用和1次动态调用。动态调用比多个静态调用慢得多,并且对JVM进行优化要困难得多,因此主导了时序。因此,只要open块需要动态调用,each
总是应该更慢。
由于Closure#调用的逻辑很复杂,因此很难优化动态调用。这很烦人,因为它并不是真正需要的,一旦找到解决方法就会被删除。如果我们能够取得成功,那么each
可能仍然会变慢,但这是一件非常困难的事情,因为字节码大小和调用配置文件在这里起作用。理论上它们可以是相同的(忽略初始化时间),但JVM还有很多工作要做。当然,这同样适用于Java8中基于流的lambda处理的例子,