【问题标题】:Why Stream/lazy val implementation using is faster than ListBuffer one为什么使用 Stream/lazy val 实现比 ListBuffer 更快
【发布时间】:2010-12-26 14:56:48
【问题描述】:

我在下面使用 Stream 和惰性 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
}

以及使用(可变)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(_

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

【问题讨论】:

  • 第二个版本说 9, 15, 21, 27 ... 是素数...
  • 对不起,我的错。我编辑了第二个版本的最后一行:
  • 我假设 second 实现比第一个快三倍?你说第一个实现比第一个实现快,这没有任何意义。 :-)
  • 另一个错误,对不起。第一个比第二个快三倍。
  • 嗯嗯。我在这里得到类似 1312 vs 1478 的结果,而不是三倍。然而,我预计第二个版本会更快.... scala 版本、编译器标志、jvm 标志、jre 版本、架构等是什么?

标签: scala stream listbuffer


【解决方案1】:

嗯,我猜是这一行:

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

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

【讨论】:

  • 你是对的!那应该是问题所在。我将这一行修改为 find(i=> ps.view.takeWhile(j => j * j 0)).get 它变得更快;甚至比第一个更快。
  • @anrizal 第一个使用lazy val,是同步的。尽管如此,我还是很惊讶它跑得更快。
  • 我用另一台机器再次尝试。它稍微大一点:4G RaM、双核、Windows 与我之前进行测试的笔记本电脑相比。我没有发现 1000000 的相同症状,但对于 2000000,第二个版本再次慢了三倍。 ps.view.takeWhile 解决了两者的问题。
【解决方案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

我还尝试用几个while 循环替换fromfindhasNoDivisor,这样更快,但更难理解。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-29
    • 1970-01-01
    • 1970-01-01
    • 2019-06-28
    • 1970-01-01
    • 2020-03-18
    • 2021-10-14
    相关资源
    最近更新 更多