基于这篇论文,我试图实现一个纯粹的Eratosthenes算法Sieve算法:https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf
按照所有步骤,我最终获得了一个非常高性能的Haskell代码,并尝试将其移植到Clojure。问题是,Clojure的版本非常慢:它与尝试测试所有数字以检查它们是否可被整除一样慢。我最终得到的代码如下:
(defn- sieve2 [[x & xs] table]
(let [reinsert (fn [table prime]
; (merge-with concat table {(+ x prime) [prime]})
(update table (+ x prime) #(cons prime %)))] ;(vec %) prime)))]
(if x
(if-let [facts (get table x)]
(recur xs (reduce reinsert (dissoc table x) facts))
(lazy-seq (cons x (sieve2 xs (assoc table (* x x) [x])))))
'())))
(defn real-sieve [xs] (sieve2 xs {}))
(与concat合并被评论,因为这是Haskell的方式,但它更慢)。
拥有30000个素数,Haskell的版本在39毫秒内运行,而Clojure在483毫秒内运行。所以,我将我的Clojure版本移植到Scala:
val primes2 = {
def sieve(xs: Stream[Int], table: Map[Int, Vector[Int]]): Stream[Int] =
xs match {
case Stream() => xs
case x #:: xs => table get x match {
case Some(facts) =>
sieve(xs, facts.foldLeft(table - x) { (table, prime) =>
val key = x + prime
val value = table.getOrElse(key, Vector()) :+ x
table + (key -> value)
})
case None => x #:: sieve(xs, table + (x*x -> Vector(x)))
}
}
sieve(Stream.from(2), Map())
}
它跑了39ms。然后,我下载了VisualVM并对我的代码进行了采样,看看:
请注意,大多数情况下,性能杀手是HashMap键查找和assoc
。我的代码有问题吗?
答案 0 :(得分:1)
尝试使用OP的代码,我确实看到scala实现大约需要30毫秒,而clojure大约需要500毫秒。那很奇怪。
所以我比较了结果,发现scala实现给了我很多偶数作为素数。经过一番挖掘后,我了解到scala实现中存在两个错误。 第一个:
val value = table.getOrElse(key, Vector()) :+ x // bug
val value = table.getOrElse(key, Vector()) :+ prime // corrected
这个错误导致评估结束得更快,因为结果中包含了大量的非素数。
scala版本的第二个错误是使用Int
。在30000到达素数之前的方式发生溢出:
scala> 92683*92683
res1: Int = 203897 // an odd square??
所以,我也解决了这个问题,因为scala没有Stream.from(Long)
,所以也必须写一下(我不会说流利的scala,所以可能有更好的方法......):
object Test {
def sieve(xs: Stream[Long], table: Map[Long, Vector[Long]]): Stream[Long] =
xs match {
case Stream() => xs
case x #:: xs = {
table get x match {
case Some(facts) =>
sieve(xs, facts.foldLeft(table - x) { (table, prime) =>
val key = x + prime
val value = table.getOrElse(key, Vector()) :+ prime
table + (key -> value)
})
case None => {
x #:: sieve(xs, table + (x*x -> Vector(x)))
}}}}
def fromLong(start:Long) : Stream[Long] = Stream.cons(start, fromLong(start+1))
def main(args: Array[String]) {
sieve(fromLong(2), Map())
}
}
再次运行这个给了我scala和clojure的可比时间:
scala> Test.time {Test.sieve(Test.fromLong(2), Map()).take(30000).last}
Elapsed time: 583 msecs
res14: Long = 350377
和clojure的版本:
(time (last (take 30000 (real-sieve a))))
"Elapsed time: 536.646696 msecs"
350377