Clojure HashMap查找的性能问题

时间:2016-11-10 23:26:13

标签: performance dictionary clojure hashmap

基于这篇论文,我试图实现一个纯粹的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并对我的代码进行了采样,看看:

Snapshot

请注意,大多数情况下,性能杀手是HashMap键查找和assoc。我的代码有问题吗?

1 个答案:

答案 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

事实上,这是http://www.theo-android.co.uk/books/sample/q=code