为什么Clojure的循环和迭代方法之间的速度有这么大的差异

时间:2012-03-26 01:10:04

标签: clojure loops

我有一个问题,为什么循环方法和clojure中的迭代方法之间的速度存在这样的差异。我按照http://www.learningclojure.com/2010/02/clojure-dojo-2-what-about-numbers-that.html中的教程使用Heron方法定义了两个平方根方法:

(defn avg [& nums] (/ (apply + nums) (count nums)))
(defn abs [x] (if (< x 0) (- x) x))
(defn close [a b] (-> a (- b) abs (< 1e-10) ) )

(defn sqrt [num]
  (loop [guess 1]
    (if (close num (* guess guess))
        guess
     (recur (avg guess (/ num guess)))
)))

(time (dotimes [n 10000] (sqrt 10))) ;;"Elapsed time: 1169.502 msecs"


;; Calculation using the iterate method
(defn sqrt2 [number]
    (first (filter #(close number (* % %)) 
        (iterate #(avg % (/ number %)) 1.0))))

(time (dotimes [n 10000] (sqrt2 10))) ;;"Elapsed time: 184.119 msecs"

这两种方法之间的速度大约有x10的增加,我想知道表面下方发生了什么导致这两种方法如此突然出现?

1 个答案:

答案 0 :(得分:5)

您的结果令人惊讶:通常,循环/重复是Clojure中用于循环的最快构造。

我怀疑JVM JIT已经为迭代方法设计了一个聪明的优化,但是没有针对loop / recur版本。令人惊讶的是,当您在Clojure中使用干净的功能代码时,这种情况经常发生:它似乎非常适合优化。

请注意,通过明确使用双打,您可以在两个版本中获得显着的加速:

(set! *unchecked-math* true)

(defn sqrt [num]
  (loop [guess (double 1)]
    (if (close num (* guess guess))
      guess
      (recur (double (avg guess (/ num guess)))))))

(time (dotimes [n 10000] (sqrt 10)))
=> "Elapsed time: 25.347291 msecs"


(defn sqrt2 [number]
  (let [number (double number)]
    (first (filter #(close number (* % %)) 
      (iterate #(avg % (/ number %)) 1.0)))))

(time (dotimes [n 10000] (sqrt 10)))
=> "Elapsed time: 32.939526 msecs"

正如预期的那样,loop / recur版本现在略有优势。结果适用于Clojure 1.3