【问题标题】:scala Iterator#foreach performance issuescala Iterator#foreach 性能问题
【发布时间】:2014-05-16 05:30:46
【问题描述】:

要迭代一个迭代器,我们可以调用它的foreach 或使用while 循环。 foreach的实现是:

def foreach[U](f: A =>  U) { while (hasNext) f(next()) }

所以我认为foreach应该和while(iterator.hasNext)一样快,但是在做了一些测试后,结果让我很惊讶。

我的测试代码:

def getSize2[T](i: Iterator[T]) = {
  var count = 0
  val f = (a: T) => count += 1
  while(i.hasNext) {
    f(i.next)
  }
  count
}

def getSize3[T](i: Iterator[T]) = {
  var count = 0
  val f = (a: T) => count += 1
  i.foreach(f)
  count
}

很奇怪getSize2getSize3快3倍!

有人知道那里发生了什么吗?

编辑: 粘贴我的测试程序

def main(args: Array[String]) {
  val data = 0 to 100000000

  val start2 = System.nanoTime
  (0 to 100).foreach(_ => getSize2(data.iterator))
  println("get size, while loop, using function: " + (System.nanoTime - start2)/1000000)

  val start3 = System.nanoTime
  (0 to 100).foreach(_ => getSize3(data.iterator))
  println("get size, foreach: " + (System.nanoTime - start3)/1000000)

}

我的操作系统:ubuntu 12.04,scala 版本:2.10.3

【问题讨论】:

  • 你能写出你是如何测量执行速度的吗?我的意思是您执行的实际代码和命令行。另外 scala 版本和您正在运行的操作系统。
  • 查看字节码,您会发现 foreach 正在执行更多调用。
  • 这很有趣,因为我看到了相反的结果。 while (iter.hasNext) { count += 1; iter.next() } 需要大约 6 倍的时间。
  • 更新:使用 Scala 2.11.12 检查此结果,我发现给定的测试代码使用具有一百万个整数的底层 mutable.ArrayBuffer 以大约 30% 的速度运行 while 循环。但是,如果将“Iterable[T]”参数更改为“Iterable[T]”(将“val i = input.iterator”添加到 while 循环中),则 while 循环的运行速度大致相同,但“foreach”在这里跑得快十倍以上。

标签: scala scala-collections


【解决方案1】:

while 循环更快,因为函数调用不是空闲的,并且不能总是被 JIT 编译器删除。特别是,var count 被包装在一个匿名对象中,因此它可以从函数对象中访问它,并且要真正加快速度,JIT 编译器需要解开所有内容,然后最终意识到它从不需要匿名对象全部。

将额外的函数调用层添加到库 foreach 确实使 JIT 编译器的分析变得复杂(三层间接而不是两层等)。

【讨论】:

  • 我还在 while 循环版本中将 var count 包装在一个匿名函数对象中。你的意思是 JIT 更有可能在 while 循环版本而不是 foreach 版本中删除函数调用?
  • @cloud - 是的,这就是我的意思。要做的工作更少,这通常会有所作为。 (编辑了答案以更清楚地说明这一点。)
猜你喜欢
  • 2011-06-21
  • 1970-01-01
  • 2016-05-17
  • 1970-01-01
  • 2015-12-16
  • 1970-01-01
  • 1970-01-01
  • 2014-05-18
  • 1970-01-01
相关资源
最近更新 更多