Scala 为什么使用Stream/lazy val实现比ListBuffer更快
我使用下面的Stream和lazy val编码了以下惰性筛选算法的实现: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 <=
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解决了这两个问题。