为什么Stream / lazy val实现使用比ListBuffer快

时间:2010-12-26 14:56:49

标签: scala stream listbuffer

我使用Stream和lazy val编写了以下懒惰筛选算法的实现:

def primes(): Stream[Int] = {
   lazy val ps = 2 #:: sieve(3)
   def sieve(p: Int): Stream[Int] = {
       p #:: sieve(
            Stream.from(p + 2, 2).
             find(i=> ps.takeWhile(j => j * j <= i).
                     forall(i % _ > 0)).get)
  }
  ps
}

以及使用(mutable)ListBuffer的以下实现:

import scala.collection.mutable.ListBuffer
def primes(): Stream[Int] = {
    def sieve(p: Int, ps: ListBuffer[Int]): Stream[Int] = {
        p #:: { val nextprime =
            Stream.from(p + 2, 2).
            find(i=> ps.takeWhile(j => j * j <= i).
                 forall(i % _ > 0)).get
            sieve(nextprime, ps += nextprime)
         }
    }       
    sieve(3, ListBuffer(3))}

当我做primes()。takeWhile(_&lt; 1000000).size时,第一个实现比第二个实现快3倍。 对此有何解释?

我编辑了第二个版本:它应该是筛子(3,ListBuffer(3))而不是筛子(3,ListBuffer())。

2 个答案:

答案 0 :(得分:6)

嗯,我的猜测是这一行:

find(i=> ps.takeWhile(j => j * j <= i).forall(i % _ > 0)).get

ListBuffer上,takeWhile创建一个临时集合(不断变大和变大)。同时,Stream由于其不严格,因此避免这样做。一旦forall失败,它就会停止计算takeWhile

答案 1 :(得分:2)

并没有真正回答这个问题,但是因为我花了一些时间对各种组合进行基准测试......

如果在内部循环中使用IteratorArrayBuffer并避免takeWhile,则可以获得更好的性能,以最大限度地减少内存分配。

def primes2(): Stream[Int] = {
  def sieve(p: Int, ps: ArrayBuffer[Int]): Stream[Int] = {
    def hasNoDivisor(prime_? :Int, j: Int = 0): Boolean = {
      val n = ps(j)
      if (n*n > prime_?) true
      else if (prime_? % n == 0) false else hasNoDivisor(prime_?, j+1)
    }
    p #:: { 
      val nextprime = Iterator.from(ps.last + 2, 2).find(hasNoDivisor(_)).get
      sieve(nextprime, ps += nextprime)
    }
  }     
  sieve(3, ArrayBuffer(3))
}

这是一个Iterator而不是Stream的版本,速度更快,如果需要,您可以随时使用primes3().toStream获取流。

def primes3() = List(2,3).iterator ++ new Iterator[Int] {
  val ps = ArrayBuffer[Int](3)
  def hasNoDivisor(prime_? :Int, j: Int = 0): Boolean = {
    val n = ps(j)
    if (n*n > prime_?) true
    else if (prime_? % n == 0) false else hasNoDivisor(prime_?, j+1)
  }
  def hasNext = true
  def next() = {
    val nextprime = Iterator.from(ps.last + 2, 2).find(hasNoDivisor(_)).get
    ps += nextprime
    nextprime
  }
}

结果:

primes : warming...
primes : running...
primes : elapsed: 3.711
res39: Int = 283145
primes2: warming...
primes2: running...
primes2: elapsed: 1.039
res40: Int = 283145
primes3: warming...
primes3: running...
primes3: elapsed: 0.530
res41: Int = 283146

我还尝试将fromfindhasNoDivisor替换为几个while循环,这样更快,但不太容易理解。