Performance Clojure在集合更新或大量计算方面的性能

Performance Clojure在集合更新或大量计算方面的性能,performance,clojure,Performance,Clojure,我正试图用筛子算法求一个Euler项目问题的素数。我使用可变集合来存储非素数的数字,并使用“dosync”和“communite”来更新该集合(否则,如果它是不可变的,我的内存就用完了)。在120万个素数之前,性能基本上是线性的,但在150万个素数时(7秒对64秒),性能非常糟糕。知道我做错了什么吗?我的猜测是,可能是数字太大了,或者是更新可变集的效率太低了 (defn mark-multiples [not-prime-set multiple prime-max] (loop [ cou

我正试图用筛子算法求一个Euler项目问题的素数。我使用可变集合来存储非素数的数字,并使用“dosync”和“communite”来更新该集合(否则,如果它是不可变的,我的内存就用完了)。在120万个素数之前,性能基本上是线性的,但在150万个素数时(7秒对64秒),性能非常糟糕。知道我做错了什么吗?我的猜测是,可能是数字太大了,或者是更新可变集的效率太低了

(defn mark-multiples [not-prime-set multiple prime-max]
  (loop [ counter (long 2) 
      product (* counter multiple)]
    (if (> product prime-max) not-prime-set
      (do
        (dosync (commute not-prime-set conj product))
        (recur  (inc counter) (* (inc counter) multiple))))))


(defn sieve-summation [prime-max]
  (def not-prime-set (ref #{ (long 1) }) )
  (loop [counter (long 2)
     summation (long 0)]
    (if (> counter prime-max) summation
      (if (not (contains? @not-prime-set counter)) 
        (do 
          (mark-multiples not-prime-set counter prime-max)
          (recur  (inc counter) (+ summation counter)))
        (recur (inc counter) summation)))))
=>(时间(筛分总和100000)) “运行时间:496.673毫秒” 454396537

=>(时间(筛分总和150000)) “运行时间:763.333毫秒” 986017447

=>(时间(筛分总和1000000)) “运行时间:6037.926毫秒” 37550402023

=>(时间(筛总和1100000)) “运行时间:6904.385毫秒” 45125753695

=>(时间(筛分总和1200000)) “运行时间:7321.299毫秒” 53433406131

=>(时间(筛分总和1500000)) “运行时间:64995.216毫秒” 82074443256

----编辑----

谢谢A.韦伯,很好的建议!你的代码有点慢,所以为了让它加速,我不得不在一开始就让非素数集暂时化,现在它运行得更快(大约8倍)。我仍然会遇到内存不足的错误,因此我将尝试找出如何增加jvm上的堆大小,看看这是否可以解决问题。我在Mac上运行Eclipse上的Clojure,我是Clojure和Mac的新手

我很想知道如何进一步重构程序(保持基本相同的逻辑),使Clojure更加优雅。再次感谢

(defn mark-multiples2 [not-prime-set prime prime-max]
  (loop [multiple (* 2 prime) nps not-prime-set ]
    (if (> multiple prime-max) 
      nps
      (recur (+ multiple prime) (conj! nps multiple)))))


(defn sieve-summation2 [prime-max]
  (loop [counter 2, summation 0, not-prime-set (transient #{1})]
    (if (> counter prime-max) 
      summation
      (if (not-prime-set counter) 
        (recur (inc counter) summation not-prime-set)
        (recur (inc counter) 
           (+ summation counter) 
           (mark-multiples2 not-prime-set counter prime-max))))))
=>(时间(筛和2 100000)) “运行时间:124.781毫秒” 454396537

=>(时间(筛分总和100000)) “运行时间:876.744毫秒”
454396537

在Clojure中有更好、更优雅的方法来解决这个问题,但这不是你问题的重点

使用引用类型——不管它是ref,或者更恰当地说是atom——在这里对您没有任何帮助。你仍然在制造同样多的垃圾。您只需将可变存储位置的内容从一个不变的数据结构交换到另一个不变的数据结构。我不知道是什么导致了你的时间尖峰,但有一种可能是你触发了一个很长的垃圾收集周期

你想在这里使用的是。在不过度更改代码的情况下,以下内容应该是一个显著的加速

(defn mark-multiples [not-prime-set multiple prime-max]
  (loop [m (* 2 multiple), nps (transient not-prime-set)]
    (if (> m prime-max) 
      (persistent! nps)
      (recur (+ m multiple) (conj! nps m)))))


(defn sieve-summation [prime-max]
  (loop [counter 2, summation 0, not-prime-set #{1}]
    (if (> counter prime-max) 
      summation
      (if (contains? not-prime-set counter) 
        (recur (inc counter) summation not-prime-set)
        (recur (inc counter) 
               (+ summation counter) 
               (mark-multiples not-prime-set counter prime-max))))))
这是同样的算法,风格更为惯用:

(defn mark [s n m]
  (into s (range (* 2 n) m n)))

(defn prime-sum [m]
  (let [step (fn [[a s] n] 
               (if (s n)
                 [a s] 
                 [(+ a n) (mark s n m)]))]
  (first (reduce step [0 #{}] (range 2 m)))))

从这里开始,您可能开始攻击算法固有的内存问题——您正在存储所有非素数,而您只需要在任何给定点存储下一个非素数。要想更好地实现这个想法,请参阅Christophe Grand的文章。

在Clojure中有更好、更优雅的方法来解决这个问题,但这不是你问题的重点

使用引用类型——不管它是ref,或者更恰当地说是atom——在这里对您没有任何帮助。你仍然在制造同样多的垃圾。您只需将可变存储位置的内容从一个不变的数据结构交换到另一个不变的数据结构。我不知道是什么导致了你的时间尖峰,但有一种可能是你触发了一个很长的垃圾收集周期

你想在这里使用的是。在不过度更改代码的情况下,以下内容应该是一个显著的加速

(defn mark-multiples [not-prime-set multiple prime-max]
  (loop [m (* 2 multiple), nps (transient not-prime-set)]
    (if (> m prime-max) 
      (persistent! nps)
      (recur (+ m multiple) (conj! nps m)))))


(defn sieve-summation [prime-max]
  (loop [counter 2, summation 0, not-prime-set #{1}]
    (if (> counter prime-max) 
      summation
      (if (contains? not-prime-set counter) 
        (recur (inc counter) summation not-prime-set)
        (recur (inc counter) 
               (+ summation counter) 
               (mark-multiples not-prime-set counter prime-max))))))
这是同样的算法,风格更为惯用:

(defn mark [s n m]
  (into s (range (* 2 n) m n)))

(defn prime-sum [m]
  (let [step (fn [[a s] n] 
               (if (s n)
                 [a s] 
                 [(+ a n) (mark s n m)]))]
  (first (reduce step [0 #{}] (range 2 m)))))

从这里开始,您可能开始攻击算法固有的内存问题——您正在存储所有非素数,而您只需要在任何给定点存储下一个非素数。关于这个想法的漂亮实现,请参阅Christophe Grand的文章。

我使用了Eclipse的逆时针插件,它使用的是Clojure 1.5,我相信是因为我有jdk 1.6。我升级到JDK1.7,并将project.clj更新为使用Clojure1.6.0,并且没有任何内存/速度问题。
谢谢你的建议。

我使用了Eclipse的逆时针插件,它使用的是Clojure 1.5,我相信是因为我有jdk 1.6。我升级到JDK1.7,并将project.clj更新为使用Clojure1.6.0,并且没有任何内存/速度问题。
谢谢你的建议。

你的jvm的堆大小是多少?我不认为-Xmx1GYou的降级应该使用Clojure 1.6-哈希经过了彻底的修改,对于这个特定的用例可能会更好。谢谢,我会尝试一下。在Mac上运行Clojure,你们都推荐什么?我感谢任何能提升我声誉的投票:)除了你选择的编辑之外,莱宁根(莱恩)是你所需要的一切。直接从站点安装,因为包管理器提供过时的版本。jvm的堆大小是多少?我不认为-Xmx1GYou的降级应该使用Clojure 1.6-哈希经过了彻底的修改,对于这个特定的用例可能会更好。谢谢,我会尝试一下。在Mac上运行Clojure,你们都推荐什么?我感谢任何能提升我声誉的投票:)除了你选择的编辑之外,莱宁根(莱恩)是你所需要的一切。直接从站点安装,因为软件包管理器提供了过时的版本。我用代码编辑了上面的问题,以加快安装速度。我很感激你的任何其他建议,谢谢。谢谢你分享这个惯用的风格。我花了一些时间才弄明白reduce是如何工作的,非常流畅。我用代码编辑了上面的问题,以加快速度。我很感激你的任何其他建议,谢谢。谢谢你分享这个惯用的风格。我花了一些时间才弄明白减压器是如何工作的,非常灵活。