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

Scala 为什么使用Stream/lazy val实现比ListBuffer更快,scala,stream,listbuffer,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 <=

我使用下面的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
}
def primes():流[Int]={
惰性值ps=2#::筛(3)
def筛(p:Int):流[Int]={
p.::筛子(
从(p+2,2)开始。
查找(i=>ps.takeWhile(j=>j*j0)).get)
}
附言
}
以及以下使用(可变)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))}
导入scala.collection.mutable.ListBuffer
def primes():流[Int]={
def筛(p:Int,ps:ListBuffer[Int]):流[Int]={
p#:{val nextprime=
从(p+2,2)开始。
查找(i=>ps.takeWhile(j=>j*j0)).get
筛子(下一次,ps+=下一次)
}
}       
筛(3,ListBuffer(3))}
当我使用primes().takeWhile(<1000000).size时,第一个实现比第二个实现快3倍。 对此有什么解释


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

好吧,我猜这一行:

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

ListBuffer
上,
takeWhile
创建一个临时集合(它会越来越大)。同时,
,由于其非严格性,避免了这样做。一旦
for all
失败,它就会停止计算
所需时间,而

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

如果使用
Iterator
ArrayBuffer
并避免在内部循环中使用
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

我还尝试用几个
while
循环来替换
中的
查找
hasNoDivisor
,这更快,但更难理解。

第二个版本说9、15、21、27。。。是素数…对不起,是我的错。我编辑了第二个版本的最后一行:我假设第二个实现比第一个快三倍?您说过第一个实现比第一个实现快,这没有任何意义。:-)另一个错误,对不起。第一个比第二个快三倍。我得到的是1312对1478,不是三次。然而,我希望第二个版本会更快。。。。scala版本、编译器标志、jvm标志、jre版本、体系结构等是什么?你说得对!这应该是问题所在。我修改了行以查找(I=>ps.view.takeWhile(j=>j*j0)).get,它变得更快了;甚至比第一个更快。@anrizal第一个使用的是同步的
lazy val
。尽管如此,我还是很惊讶它跑得更快。我用另一台机器又试了一次。它比我以前测试的笔记本电脑稍大一些:4G内存、双核、Windows。我没有发现1000000人出现同样的症状,但对于2000000人,第二个版本的速度又慢了三倍。不过,ps.view.takeWhile解决了这两个问题。