为什么Scala中的foldLeft前面的过滤器速度慢?

时间:2011-01-25 16:00:05

标签: performance scala scala-collections

我写了第一个Project Euler问题的答案:

  

添加1000以下的所有自然数,即3或5的倍数。

我遇到的第一件事是:

(1 until 1000).filter(i => (i % 3 == 0 || i % 5 == 0)).foldLeft(0)(_ + _)

但它很慢(需要125毫秒),所以我重写了它,只是想到'另一种方式'而不是'更快的方式'

(1 until 1000).foldLeft(0){
    (total, x) =>
        x match {
            case i if (i % 3 == 0 || i % 5 ==0) => i + total // Add
            case _ => total //skip
        }
}

这要快得多(仅2毫秒)。为什么?我猜第二个版本只使用Range生成器,并没有以任何方式显示完全实现的集合,一次完成所有这一切,更快,内存更少。我是对的吗?

这里是IdeOne上的代码:http://ideone.com/GbKlP

5 个答案:

答案 0 :(得分:24)

正如其他人所说,问题是filter创建了一个新的集合。备选withFilter没有,但没有foldLeft。此外,使用.view.iterator.toStream都可以避免以各种方式创建新集合,但它们在这里比您使用的第一种方法慢,我觉得有点奇怪第一

但是,然后......看,1 until 1000Range,其大小实际上非常小,因为它不存储每个元素。此外,Range的{​​{1}}已经过极优化,甚至是foreach,而其他任何集合都不是这种情况。由于specializedfoldLeft实现的,只要您使用foreach,就可以享受优化方法。

Range

(_: Range).foreach

@inline final override def foreach[@specialized(Unit) U](f: Int => U) { if (length > 0) { val last = this.last var i = start while (i != last) { f(i) i += step } f(i) } }

(_: Range).view.foreach

def foreach[U](f: A => U): Unit = iterator.foreach(f)

(_: Range).view.iterator

override def iterator: Iterator[A] = new Elements(0, length) protected class Elements(start: Int, end: Int) extends BufferedIterator[A] with Serializable { private var i = start def hasNext: Boolean = i < end def next: A = if (i < end) { val x = self(i) i += 1 x } else Iterator.empty.next def head = if (i < end) self(i) else Iterator.empty.next /** $super * '''Note:''' `drop` is overridden to enable fast searching in the middle of indexed sequences. */ override def drop(n: Int): Iterator[A] = if (n > 0) new Elements(i + n, end) else this /** $super * '''Note:''' `take` is overridden to be symmetric to `drop`. */ override def take(n: Int): Iterator[A] = if (n <= 0) Iterator.empty.buffered else if (i + n < end) new Elements(i, i + n) else this }

(_: Range).view.iterator.foreach

当然,这甚至不计算def foreach[U](f: A => U) { while (hasNext) f(next()) } filter之间的view

foldLeft

答案 1 :(得分:12)

首先尝试使集合变得懒惰,所以

(1 until 1000).view.filter...

而不是

(1 until 1000).filter...

这应该避免建立中间集合的成本。使用sum而不是foldLeft(0)(_ + _)可能也会获得更好的性能,某些集合类型总是可能有更有效的方法来对数字求和。如果没有,它仍然更清洁,更具说服力......

答案 2 :(得分:3)

查看代码,看起来filter确实构建了一个调用foldLeft的新Seq。第二个跳过那一点。它不是那么多的内存,虽然它不能不帮助,但过滤的集合根本就没有构建。所有这些工作都没有完成。

范围使用TranversableLike.filter,如下所示:

def filter(p: A => Boolean): Repr = {
  val b = newBuilder
  for (x <- this) 
    if (p(x)) b += x
  b.result
}

我认为第4行的+=就是差异。在foldLeft中过滤会消除它。

答案 3 :(得分:1)

filter创建一个全新的序列,然后调用foldLeft。尝试:

(1 until 1000).view.filter(i => (i % 3 == 0 || i % 5 == 0)).reduceLeft(_+_)

这将阻止所述效果,仅仅包裹原始物品。与foldLeft交换reduceLeft只是装饰性的(在这种情况下)。

答案 4 :(得分:1)

现在面临的挑战是,您能想到一种更有效的方式吗?在这种情况下,并不是说你的解决方案太慢了,但它的扩展程度如何?如果不是1000,那就是10亿?有一种解决方案可以像前者一样快速地计算后一种情况。