Groovy: for..in 比 .each 快很多吗?

Groovy: Is for..in significantly faster than .each?

我很好奇,出于性能原因,for..in 是否应该优于 .each

For .. in 是标准语言流控制的一部分。

而是 each 调用一个闭包,因此会产生额外的开销。

.each {...} 是相当于方法调用 .each({...})

的语法糖

此外,由于它是一个闭包,在each代码块中你不能使用breakcontinue语句来控制循环。

http://kunaldabir.blogspot.it/2011/07/groovy-performance-iterating-with.html

更新基准 Java 1.8.0_45 Groovy 2.4.3:

  • 3327981 个{}
  • 320949 for(){

这是另一个具有 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

结果:

  • 261062715 个{}
  • 64518703 for(){}

让我们从理论上看一下哪些调用是动态完成的,哪些调用更直接地使用 Java 逻辑(我将调用那些静态调用)。

for-in 的情况下,Groovy 对 Iterator 进行操作,要获取它,我们需要对 iterator() 进行一次动态调用。如果我没记错的话, hasNext 和 next 调用是使用正常的 Java 方法调用逻辑完成的。因此,对于每次迭代,我们这里只有 2 个静态调用。根据基准,必须注意第一个 iterator() 调用可能会导致严重的初始化时间,因为这可能会初始化 meta class 系统并且需要一些时间。

each 的情况下,我们有对每个自身的动态调用,以及为打开的块(闭包实例)创建对象。 each(Closure) 然后也会调用 iterator() ,但未缓存......所有一次性成本。在循环中 hasNext 和 next 是使用 Java 进行 2 次静态调用的逻辑完成的。对 Closure 实例的调用是使用方法调用的 java 标准逻辑完成的,然后将使用动态调用调用 doCall 。

总而言之,每次迭代 for-in 仅使用 2 次静态调用,而 each 有 3 次静态调用和 1 次动态调用。动态调用比多个静态调用慢得多,并且更难针对 JVM 进行优化,因此在时间上占主导地位。因此 each 应该总是更慢,只要打开块需要动态调用。

由于Closure#call 的逻辑复杂,很难优化动态调用。这很烦人,因为它并不是真正需要的,一旦我们找到解决方法就会将其删除。如果我们成功了,each 可能仍然更慢,但这是一件更困难的事情,因为字节码大小和调用配置文件在这里发挥作用。理论上它们可以相等(忽略初始化时间),但 JVM 有更多的工作要做。当然,这同样适用于 Java8、

中基于流的 lambda 处理示例