atomの配列と配列のatom

Clojureは基本的にmutableなものを許さない設計になっているので、do-syncなどのトランザクションの中でrefを変更するコストは結構高い(はず)。ドランザクションではないけど、atomの場合でちょっと実験してみる。

atomの配列と配列のatomを用意。要素をランダムに置き換えていくような実験の設定。

(def x (atom '[1 2 3]))
(def y [(atom 1) (atom 2) (atom 3)])
(def N 100000)

(println "x")
(dotimes [_ 5]
  (let [rand-seq (doall (vec (map (fn [_] (rand-int 3)) (range N))))]
    (time (dotimes [i N]
	    (let [n (nth rand-seq i)]
	      (swap! x assoc n n))))))

(println "y")
(dotimes [_ 5]
  (let [rand-seq (doall (vec (map (fn [_] (rand-int 3)) (range N))))]
    (time (dotimes [i N]
	    (let [n (nth rand-seq i)]
	      (reset! (y n) n))))))

要素のatomを持たせたほうが変更のときのコストが小さいようだ。

x
"Elapsed time: 261.819 msecs"
"Elapsed time: 251.127 msecs"
"Elapsed time: 242.16 msecs"
"Elapsed time: 240.869 msecs"
"Elapsed time: 256.606 msecs"
y
"Elapsed time: 108.052 msecs"
"Elapsed time: 119.755 msecs"
"Elapsed time: 79.847 msecs"
"Elapsed time: 82.929 msecs"
"Elapsed time: 79.497 msecs"

要素にatomを持たせるとメモリを結構食いそうな気がするが、今考えているやつだとメモリよりも実行時間のほうが重要なので、要素にatomのほうの設計がいいかもしれない(もちろん、配列の要素間で変なことが起きたりすると困る例では要素毎にatomは論外だろう)。

違う設定で実験

配列の長さをもっと増やしてみた。

(def x (atom (vec (range 10000))))
(def y (vec (for [i (range 10000)] (atom i))))

(def N 100000)

(println "x")
(dotimes [_ 5]
  (let [rand-seq (doall (vec (map (fn [_] (rand-int 10000)) (range N))))]
    (time (dotimes [i N]
	    (let [n (nth rand-seq i)]
	      (swap! x assoc n n))))))

(println "y")
(dotimes [_ 5]
  (let [rand-seq (doall (vec (map (fn [_] (rand-int 10000)) (range N))))]
    (time (dotimes [i N]
	    (let [n (nth rand-seq i)]
	      (reset! (y n) n))))))

やはり配列のatomのほうが遅くなっている度合いが大きいた思ったほどでもなかったような気がする。

x
"Elapsed time: 780.831 msecs"
"Elapsed time: 701.475 msecs"
"Elapsed time: 695.271 msecs"
"Elapsed time: 651.421 msecs"
"Elapsed time: 646.095 msecs"
y
"Elapsed time: 187.253 msecs"
"Elapsed time: 201.147 msecs"
"Elapsed time: 151.437 msecs"
"Elapsed time: 126.56 msecs"
"Elapsed time: 126.16 msecs"

プログラミングClojure

プログラミングClojure