如何使这个命令(数组,循环等)Clojure代码的运行速度是Java的一半?

时间:2014-03-28 02:24:31

标签: arrays performance clojure

我已经读过有一些方法可以哄骗Clojure编译器生成可以与Java中类似代码的性能相媲美的代码,至少对于那些看起来很像你希望它变成Java代码的代码来说。这听起来对我来说是合理的:惯用的,高级的Clojure代码可能会在我从CPython或MRI中使用的表现中得到表现,但是"丑陋的"类似Java的代码或多或少地像Java一样运行。例如,这是我在Haskell中所欣赏的权衡。低级Haskell代码,带有可变数组,循环和不能在GHC下使用适当的编译器标志运行的速度与在C中一样快(然后一些高科技库有时可以从更漂亮,更高级别的代码中挤出类似的性能)。 / p>

我想要帮助学习如何让我的Java类Clojure代码像Java一样快速运行。举个例子:

(defn f [x y z n]
  (+ (* 2 (+ (* x y) (+ (* y z) (* x z))))
     (* 4 (+ x y z n -2) (- n 1))))

(defmacro from [[var ini cnd] & body]
  `(loop [~var ~ini]
     (when ~cnd
       ~@body
       (recur (inc ~var)))))

(defn g [n]
  (let [c (long-array (inc n))]
    (from [x 1 (<= (f x x x 1) n)]
      (from [y x (<= (f x y y 1) n)]
        (from [z y (<= (f x y z 1) n)]
          (from [k 1 (<= (f x y z k) n)]
            (let [l (f x y z k)]
              (aset c l (inc (aget c l))))))))
    c))

(defn h [x]
  (loop [n 1000]
    (let [^longs c (g n)]
      (if-let [k (some #(when (= x (aget c %)) %)
                       (range 1 (inc n)))]
        k
        (recur (* 2 n))))))

(time (print (h 1000)))

在我的(不可否认的)慢速机器上使用Clojure 1.6需要大约85秒。 Java中的等效代码在大约0.4秒内运行。我并不贪心,我只想让Clojure代码运行,比如大约2秒钟。

我做的第一件事就是启用*warn-on-reflection*,但遗憾的是,对于那种孤独的类型提示,没有进一步的警告。我做错了什么?

This gist包含代码的Java和Clojure版本。

1 个答案:

答案 0 :(得分:8)

不幸的是*warn-on-reflection*并没有警告你原始拳击 - 我认为这是主要的问题。您希望始终使用未装箱的原始算法以获得最大速度。

以下提示应该可以帮助您优化:

  • 执行(set! *unchecked-math* true)以获得更快的原始数值运算
  • 尝试使用(long ~ini)初始化循环。你想以这种方式强制使用原语
  • 尝试将原始提示^long n添加到函数g
  • 尝试对长数组^longs c进行类型提示 - 这有望使Clojure使用更快的原语aget
  • 键入提示f作为基本函数^long [^long x ^long y ^long z ^long n]或类似内容。这非常重要,否则f将返回盒装数字....

如果你成功消除了所有盒装数字,那么这种代码应该和纯Java一样快。