我已经读过有一些方法可以哄骗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版本。
答案 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一样快。