为什么Clojure比等效函数的mit方案快得多?

时间:2012-04-30 13:54:27

标签: clojure scheme mit-scheme

我在Clojure中找到了这个代码来筛选出第一个 n 素数:

(defn sieve [n]
  (let [n (int n)]
    "Returns a list of all primes from 2 to n"
    (let [root (int (Math/round (Math/floor (Math/sqrt n))))]
      (loop [i (int 3)
             a (int-array n)
             result (list 2)]
        (if (>= i n)
          (reverse result)
          (recur (+ i (int 2))
                 (if (< i root)
                   (loop [arr a
                          inc (+ i i)
                          j (* i i)]
                     (if (>= j n)
                       arr
                       (recur (do (aset arr j (int 1)) arr)
                              inc
                              (+ j inc))))
                   a)
                 (if (zero? (aget a i))
                   (conj result i)
                   result)))))))

然后我在Scheme中编写了等效(我认为)的代码(我使用mit-scheme)

(define (sieve n)
  (let ((root (round (sqrt n)))
        (a (make-vector n)))
    (define (cross-out t to dt)
      (cond ((> t to) 0)
            (else
             (vector-set! a t #t)
             (cross-out (+ t dt) to dt)
             )))
    (define (iter i result)
      (cond ((>= i n) (reverse result))
            (else
             (if (< i root)
                 (cross-out (* i i) (- n 1) (+ i i)))
             (iter (+ i 2) (if (vector-ref a i)
                               result
                               (cons i result))))))
    (iter 3 (list 2))))

时间结果如下: 对于Clojure:

(time (reduce + 0 (sieve 5000000)))
"Elapsed time: 168.01169 msecs"

对于mit-scheme:

(time (fold + 0 (sieve 5000000)))
"Elapsed time: 3990 msecs"

有谁能告诉我为什么mit-scheme慢了20多倍?

update :&#34;区别在于iterpreted / compiled模式。编译完mit方案代码后,它的运行速度相当快。 - abo-abo Apr 30 '12 at 15:43&#34;

2 个答案:

答案 0 :(得分:15)

与解释型语言相比,Java虚拟机的现代版本具有极佳的性能。大量的工程资源已经进入JVM,特别是热点JIT编译器,高度调整的垃圾收集等等。

我怀疑你看到的差异主要取决于那个。例如,如果查看Are the Java programs faster? ,您可以看到java与ruby的比较,这表明java在其中一个基准测试中的性能优于220倍。

您没有说明运行clojure基准测试的JVM选项。尝试使用-Xint标志运行java,该标志以纯解释模式运行,看看有什么区别。

此外,您的示例可能太小而无法真正预热JIT编译器。使用更大的示例可能会产生更大的性能差异。

让您了解Hotspot对您有多大帮助。我在我的MBP 2011(四核2.2Ghz)上运行了你的代码,使用带有默认opts(-server hotspot)和解释模式(-Xint)的java 1.6.0_31并看到了很大的差异

; with -server hotspot (best of 10 runs)
>(time (reduce + 0 (sieve 5000000)))
"Elapsed time: 282.322 msecs"
838596693108

; in interpreted mode using -Xint cmdline arg
> (time (reduce + 0 (sieve 5000000)))
"Elapsed time: 3268.823 msecs"
838596693108

答案 1 :(得分:5)

至于比较Scheme和Clojure代码,在Clojure端有一些简化的事情:

  • 不要在循环中重新绑定可变数组;
  • 删除许多明确的原始强制,没有性能变化。从Clojure开始,如果这样的函数签名可用,函数调用中的1.3文字会编译成基元,并且通常性能上的差异很小,以至于它很快就会被循环中发生的任何其他操作淹没;
  • 在fn签名中添加原始长注释,从而删除n;
  • 的重新绑定
  • 不需要调用Math / floor - int强制具有相同的语义。

代码:

(defn sieve [^long n]
 (let [root (int (Math/sqrt n))
       a (int-array n)]
   (loop [i 3, result (list 2)]
     (if (>= i n)
       (reverse result)
       (do
         (when (< i root)
           (loop [inc (+ i i), j (* i i)]
             (when (>= j n) (aset a j 1) (recur inc (+ j inc)))))
         (recur (+ i 2) (if (zero? (aget a i))
                          (conj result i)
                          result)))))))