雑にだけれどtransient+persistent!の評価

すごく簡単な初期化のような処理じゃない限りそこまで速度に差はない。

(let [xs (range 1 1000)]
  (time (dotimes [i 1000] (reduce conj [] xs)))
  (time (dotimes [i 1000]
	  (persistent!
	   (reduce conj! (transient []) xs))))) 
;; ; =>
;;  "Elapsed time: 107.493219 msecs"
;;  "Elapsed time: 31.869933 msecs"


(defn random-char [& x]
  (char (+ 33 (rand-int 93))))
(def random-char-seq (iterate random-char \a))

(defn count-char [xs]
  (reduce (fn [m x] (assoc m x (+ 1 (get m x 0))))
	  {} xs))

(defn count-char2 [xs]
  (persistent!
   (reduce (fn [m x] (assoc! m x (+ 1 (get m x 0))))
	  (transient {}) xs)))

(let [xs (take 10000 random-char-seq)]
  (time (dotimes [i 100] (count-char xs)))
  (time (dotimes [i 100] (count-char2 xs)))
  nil)
;; ;=>
;; "Elapsed time: 752.251999 msecs"
;; "Elapsed time: 625.614659 msecs"

こういう結果がでることもあるけれど

(defn vrange [n]
  (loop [i 0 v []]
    (if (< i n)
      (recur (inc i) (conj v i))
      v)))
 
(defn vrange2 [n]
  (loop [i 0 v (transient [])]
    (if (< i n)
      (recur (inc i) (conj! v i))
      (persistent! v))))

(let [n 1000000]
  (time (vrange n))
  (time (vrange2 n))) 
; => 
;; "Elapsed time: 605.466145 msecs"
;; "Elapsed time: 54.237211 msecs"

はじめのうちはtransientとか気にせず書いていって、どうしてももう少しだけ速度が欲しい…とか、ループの深いところで使っているという時にtransientを利用すると良さそう。単純なprototypeの作成などでtransientを使うのはpedantic