3

これは、Clojureに本当に精通している人々への質問になります。
JavaとClojureで簡単な素数チェック関数を書いて、実行時間を比較したかったのです。
これがJavaの私のコードです:

import java.util.LinkedList;

public class Primes {
public static void main(String args[])
{
    long start = System.nanoTime();
    getPrimes(10000);
    long end = System.nanoTime();

    System.out.println(((float)(end - start)/1000000) + "ms");
}

private static LinkedList<Integer> getPrimes(int n)
{
    int count = 0;
    int current = 1;
    LinkedList<Integer> primes = new LinkedList<Integer>();

    while(count <= n)
    {
        if(isPrime(current))
        {
            primes.add(current);
            count++;
        }
        current++;
    }
    return primes;
}

private static boolean isPrime(long n)
{
    if(n <= 0) return false;
    if(n == 1 || n == 2) return true;
    if(n % 2 == 0) return false;
    for(int i=3; i<Math.sqrt(n) + 1; i=i+2)
    {
        if(n % i == 0){
            return false;
        }
    }
    return true;
}
}

そして、これがClojureに相当するものです:

(defn prime? [n]
  (or (= n 2) (not (some #(zero? (rem n %)) (conj (range 3 (inc (Math/sqrt n)) 2) 2)))))

(defn printPrimes [n] (take n (filter prime? (iterate inc 1))))

(defn ExecTime [function & arguments] (let [start (System/nanoTime), return (dorun (apply function arguments)), end (System/nanoTime)] (/ (- end start) 1000000.0)))

(ExecTime printPrimes 10000)

今、私が確信していないことがいくつかあります:

  1. (let)とバインディングの開始時間と終了時間は、Clojureでの実行時間を測定するのに問題ありませんか?
  2. 何らかの理由で(JavaバージョンとClojureバージョンは同じアルゴリズムを使用していますが)、Clojureバージョンははるかに低速です(J:〜50ms、C:〜400ms)。私は何か間違ったことをしていますか?

明らかな間違いを犯した場合は失礼しますが、Clojureの学習段階にあります...

- - -編集 - - -

私はそれを最適化し、最終的にはJavaと同じ時間を達成しました。興味のある人のためにブログで説明しています:http:
//blog.programmingdan.com/ ?p = 35

4

3 に答える 3

3

これを計時するその方法は非常に一般的であり、組み込みです

user> (time (reduce + (range 1000)))                                                                                                                                      
"Elapsed time: 1.350419 msecs"                                                                                                                                            
499500

ベンチマークの観点からはそれをきちんと行うために、Hugo DuncansのCriteriumライブラリを使用し、それを使用する方法についてこの投稿を読むことをお勧めします。clojureコードを高速に実行することに関しては、clojureバージョンはほとんどの時間をseqオブジェクトの割り当てに費やしています。

于 2013-03-16T01:58:45.093 に答える
2

someあなたはJavaコードに早く戻ってきたので、同等のものはではなく、だと思いますevery。例えば:

(defn prime? [n]
  (or (= n 2)
      (and (odd? n)
           (not (some #(= 0 (mod n %)) 
                      (range 3 (inc (Math/sqrt n))))))))

(time (doall (filter prime? (range 10000))))

私のマシンでは、Javaバージョンとほぼ同じように実行されます。

ところで:1は素数とは見なされないと思います。

于 2013-03-16T02:44:32.107 に答える
2

おそらく、上記の答えのように怠惰なシーケンスを扱っています。これが私の証拠です。

;; original prime? function
(defn prime? [n]
  (or (= n 2) 
      (not (some #(zero? (rem n %)) 
                 (conj (range 3 
                              (inc (Math/sqrt n)) 
                              2) 
                       2)))))

;; prime? function using recur
(defn prime?-recur [num]
  (cond (< num 2) false
        (= num 2) true
        (zero? (mod num 2)) false
        :else (loop [n num
                     i 3]
                (cond (>= i (inc (Math/sqrt n))) true
                      (zero? (mod n i)) false
                      (< i (inc (Math/sqrt n))) (recur n (+ i 2))))))

;; original printPrimes with option for testing both prime? funs
;; note I changed this to start on 2 since 1 is not prime
(defn printPrimes [n fn] (take n (filter fn (iterate inc 2))))

;; printPrimes using recursion
(defn printPrimes-recur [num fn]
  (loop [n num i 2 primes []]
    (cond (and (fn i) (< (count primes) n)) (recur n (+ i 1) (conj primes i))
          (< (count primes) n) (recur n (+ i 1) primes)
          :else primes)))

それでは、これらを実行してみましょう。まず、新しいコードが元のコードと一致することを確認します。

foo> (= (printPrimes 10000 prime?)
        (printPrimes 10000 prime?-recur)
        (printPrimes-recur 10000 prime?)
        (printPrimes-recur 10000 prime?-recur))

true

そして今、しばらくの間!(ExecTime関数を使用)

foo> (println (ExecTime printPrimes 10000 prime?)
              (ExecTime printPrimes 10000 prime?-recur)
              (ExecTime printPrimes-recur 10000 prime?)
              (ExecTime printPrimes-recur 10000 prime?-recur))



575.977 166.691 548.363 141.356
nil

それで、私たちは素数を変えるのを見ますか?再帰を使用する関数はかなり大きな違いをもたらし(約4倍高速)、再帰を使用するようにprintPrimes関数を変更すると違いも生じますが、それはほんのわずかな違いです。私のコンピューターでJavaバージョンにかかる時間はわかりませんが、少なくとも上記の時間から、seqsを使用した元のclojureバージョンよりもloop/recurバージョンの方が高速であることがわかります。

注:タイプヒント(http://clojure.org/java_interop#Java%20Interop-Type%20Hints、http://kotka.de/blog/2012/06/Did_you_know_IX.html )を試して、速度を上げることもできます。 、しかし私がそれを試したとき、それは違いを生みませんでした。それは、私が以前にそれをあまり行ったことがなかったためである可能性があり、それで私はそれを不適切に行っていた可能性があります。

于 2013-03-17T17:30:48.920 に答える