5

手動ロックを使用して、Clojure の同時実行性の「公式」の例をJava バージョンに近づけようとしています。この要点では、Java と clojure のコードと、すべてのバージョンの VisualVm プロファイルのスレッド ダンプを入れました。これがclojureコードとタイミングです

(ns simple-example (:gen-class))
(set! *warn-on-reflection* true)
;; original from: http://clojure.org/concurrent_programming
(import '(java.util.concurrent Executors Future)
SimpleLocking$Node)

(defn test-concur [iter refs nthreads niters]
  (let [pool (Executors/newFixedThreadPool nthreads)
        tasks (map (fn [t]
                      (fn []
                        (dotimes [n niters]
                          (iter refs t))))
                   (range nthreads))]
    (doseq [^Future future (.invokeAll pool tasks)]
      (.get future))
    (.shutdown pool)))

(defn test-stm [nitems nthreads niters]
  (let [refs (vec (map ref (repeat nitems 0)))
        iter #(dosync (doseq [r %] (alter r + 1 %2)))]
    (test-concur iter refs nthreads niters)
    (map deref refs)))

(defn test-atom [nitems nthreads niters]
  (let [refs (vec (map atom (repeat nitems 0)))
        iter #(doseq [r %] (swap! r + 1 %2))]
    (test-concur iter refs nthreads niters)
    (map deref refs)))

;; SimpleLocking$Node is the class with the synchronized method of java version
(defn test-locking [nitems nthreads niters]
  (let [refs (->> (repeatedly #(SimpleLocking$Node.))
                    (take nitems) vec)
        iter #(doseq [^SimpleLocking$Node n %] (.sum n (+ 1 %2)))]
    (test-concur iter refs nthreads niters)
    (map (fn [^SimpleLocking$Node n] (.read n)) refs)))

(definterface INode
  (read [])
  (add [v]))

(deftype Node [^{:unsynchronized-mutable true} value]
  INode
  (read [_] value)
  (add [this v] (set! value (+ value v))))

(defn test-locking-native [nitems nthreads niters] 
  (let [refs (->> (repeatedly #(Node. 0))
          (take nitems) vec) 
    iter #(doseq [^Node n %]
          (locking n (.add n (+ 1 %2))))]
    (test-concur iter refs nthreads niters)
    (map (fn [^Node n] (.read n)) refs)))

(defn -main [& args]
  (read-line)
  (let [[type nitems nthreads niters] (map read-string args)
    t #(apply + (time (% nitems nthreads niters)))]
    (case type
      'lock (println "Locking:" (t test-locking)) 
      'atom (println "Atom:" (t test-atom))
      'stm (println "STM:" (t test-stm))
      'lock-native (println "Native locking:" (t test-locking-native)))))

時間 (「古い」Intel Core デュオ):

Java version
int nitems=100;
int nthreads=10;
final int niters=1000;
Sum node values: 5500000
Time: 31

simple-example=> (-main "lock" "100" "10" "1000")
"Elapsed time: 60.030324 msecs"
Locking: 5500000
nil
simple-example=> (-main "atom" "100" "10" "1000")
"Elapsed time: 202.309477 msecs"
Atom: 5500000
nil
simple-example=> (-main "stm" "100" "10" "1000")
"Elapsed time: 1830.568508 msecs"
STM: 5500000
nil
simple-example=> (-main "lock-native" "100" "10" "1000")
"Elapsed time: 159.730149 msecs"
Native locking: 5500000
nil

注: 私は、Java バージョンと同じくらい速い clojure バージョンや、ロック 1 を使用する clojure と同じくらい速い stm バージョンを取得したくありません。私はそれが一般的に困難であり、いくつかの問題では不可能であることを知っています. アトムと stm の使用は、手動ロックを使用するよりも構成可能で、使いやすく、エラーが発生しにくいことを知っています。これらのバージョンは、問題に対する Java および clojure の可能な限り最良の指示対象にすぎません (私は最善を尽くしました)。私の目的は、atom と stm のバージョンをロックするバージョンに近づけるか、(おそらくこの具体的な例で) これらのバージョンを高速化できない理由を理解することです。

注: 今回は、STM と MVars を使用した Haskell バージョンとの別の比較 (リンクされた同じ要点のコード):

>SimpleExampleMVar 100000 1000 6
Starting...
2100000000
Computation time: 11.781 sec
Done.

>SimpleExampleSTM 100000 1000 6
Starting...
2100000000
Computation time: 53.797 sec
Done.

>java -cp classes SimpleLocking
Sum node values: 2100000000
Time: 15.703 sec

java -cp classes;%CLOJURE_JAR% simple_example lock 1000 6 100000
"Elapsed time: 27.545 secs"
Locking: 2100000000

java -cp classes;%CLOJURE_JAR% simple_example lock-native 1000 6 100000
"Elapsed time: 80.913 secs"
Native locking: 2100000000

java -cp classes;%CLOJURE_JAR% simple_example atom 1000 6 100000
"Elapsed time: 95.143 secs"
Atom: 2100000000

java -cp classes;%CLOJURE_JAR% simple_example stm 1000 6 100000
"Elapsed time: 990.255 secs"
STM: 2100000000
4

1 に答える 1

1

ここのように実際に比較しているわけではありません-Clojureバージョンは新しい不変のボックス化された数値を作成して交換していますが、Javaバージョンはint同期メソッドで可変プリミティブカウンターをぶつけているだけです。

次のような方法で、Clojure で通常の Java スタイルの手動ロックを行うことができます。

(locking obj (set! (. obj fieldName) (+ 1 (.fieldName obj)))))

この構造は、事実上、Javaコード ブロックlockingと同等です。synchronized

型ヒント付き Java オブジェクトまたはフィールドを持つ Clojure deftype のいずれかを使用してこれを行うと、:unsynchronized-mutable純粋な Java 同期パフォーマンスに匹敵することができるはずです。

これはテストしていませんが、プリミティブでも機能するはずです (longカウンターをインクリメントする場合などに役立つ可能性があります)。

于 2012-09-19T02:27:53.867 に答える