为什么这个Clojure代码与Java中的替代代码相比如此之慢?

Tom*_*ich 2 java arrays performance clojure clojure-java-interop

tl; dr:为什么代码如此慢?

我尝试优化以下代码以提高速度; 它的目的是在进行n ^ 2次操作时将一个数组(大小n = 1000)转换为另一个(相同大小),转换的细节现在并不重要.

因为我试图尽可能快地获得速度,所以我尽可能使用Java原语; 仍然,我得到的通常是每个'转换'调用大约70毫秒.重写到Java时,平均调用时间小于2毫秒.

1)哇,Java很快

2)哇,Clojure很慢

3)你能解释一下,为什么会这样?天真地,我希望Clojure能够生成一个代码字节码,它应该非常接近Java,为什么不是这样呢?

4)我不是百分百肯定如何使用这些^提示,也许我弄错了?

(defn transform [^ints src]
  (let [res ^ints (make-array Integer/TYPE 1000)]
    (loop [x (int 0)]
      (if (= 1000 x) res
        (do
          (aset res x (areduce src i ret (int 0) 
            (+ ret (* (mod x 2) (mod i 3) (aget src i)))))
          (recur (inc x)))))))

(let [arr (into-array Integer/TYPE (range 1000))]
  (doseq [_ (range 20)]
      (println (time (transform arr)))
  ))
Run Code Online (Sandbox Code Playgroud)

Ale*_*ler 15

这样的事情应该更接近:

(set! *warn-on-reflection* true)
(set! *unchecked-math* :warn-on-boxed)

(defn inner ^long [^ints src ^long x]
  (let [len (alength src)]
    (loop [i 0 acc 0]
      (if (< i len)
        (recur (inc i) (+ acc (* (rem x 2) (rem i 3) (aget src i))))
        acc))))

(defn transform [^ints src]
  (let [res ^ints (int-array 1000)]
    (loop [x 0]
      (if (= 1000 x) 
        res
          (do
            (aset res x (inner src x))
            (recur (inc x)))))))

(defn bench []
  (let [arr (int-array (range 1000))]
    (doseq [_ (range 20)]
      (println (time (transform arr))))))
Run Code Online (Sandbox Code Playgroud)

顶部设置对于检测错误很有用.:warn-on-boxed one是Clojure 1.7中的新功能(目前处于测试阶段,还没有完全出现),但在这里特别有用.

我改变了一些重要的事情:

  • 我替换了areduce - areduce的问题是它不知道你的数组的原始类型.通过编写自己的内部循环,您可以利用提示.有可能提示areduce的主体以使其工作但我倾向于在进行原始数学时更喜欢显式循环.
  • 我在需要的地方使用^长提示,因为Clojure只支持原始的长参数/返回,而不是int.将根据需要插入正确的基本转换.如果需要,有些函数可以获得原始的int溢出语义.
  • rem可以转到mod不能的原始操作.我认为这里的语义与你正在做的事情是一样的.这是大多数拳击的来源.
  • 我正在使用int-array而不是其他方式来制作数组.我认为这是你正在做的最好的方式.

您可以将两个循环合并为一个,并进一步提高性能.