Groovy:for..in明显快于.each?

Ale*_*hel 8 groovy microbenchmark

我很好奇是否for..in应该.each因性能原因而优先考虑.

frh*_*ack 5

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

而是each调用闭包,这样会产生额外的开销。

.each {...} 语法糖等同于方法调用 .each({...})

而且,由于它是一个闭包,each因此不能在代码块内部使用breakand continue语句来控制循环。

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

结果:

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

  • 这是四年的数据。永远不要相信自己没有操纵的基准。 (2认同)

bla*_*rag 5

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

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

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

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

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