【发布时间】:2017-03-25 03:23:06
【问题描述】:
我试图根据这篇论文实现一个纯函数式的 Eratosthenes 算法筛: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())
}
它运行时间为 39 毫秒。然后,我下载了 VisualVM 并对我的代码进行了采样,以查看:
请注意,大多数时候,性能杀手是 HashMap 键查找和assoc。我的代码有问题吗?
【问题讨论】:
-
您是否尝试使用 this map 而不是核心库中的映射?
-
你试过瞬态吗?
-
是的,我尝试过使用 int-map,并且得到了相同的结果。不,我没有尝试过瞬态,因为我想要一种纯函数式的方法——Scala 为我提供了不可变的映射,而不会影响性能。
-
我相信您的 scala 实现中有一个错误,可能会影响其运行时间。在
val value = table.getOrElse(key, Vector()) :+ x行中,您需要添加prime而不是x。在我的尝试中,这使两个版本的经过时间具有可比性(给定相似的堆大小等)
标签: performance dictionary clojure hashmap