0

这是@cgrand对“Clojure Performance For Expensive Algorithms”问题的回答的后续。我一直在研究它,并尝试将他的一些技术应用到我自己的实验性 Clojure 性能调优中。

我想知道的一件事是“丑陋的”“数组交换技巧”

      (set! curr prev)
      (set! prev bak)

与原始方法相比,这如何以及为什么会提高性能?我怀疑 Clojure 数组有时不是真正的 Java 原始数组?如有必要,请在您的回答中引用 Clojure 核心源。

4

2 回答 2

0

事实上,它与对象分配有关。这是原始算法,带有注释:

(defn my-lcs [^objects a1 ^objects a2]
  (first
    (let [n (inc (alength a1))]
      (areduce a1 i 
        ;; destructuring of the initial value 
        [max-len ^ints prev ^ints curr] 
        ;; initial value - a vector of [long int[] int[]]
        [0 (int-array n) (int-array n)] 
        ;; The return value: a vector with the prev and curr swapped positions.
        [(areduce a2 j max-len (unchecked-long max-len)  ;; 
           (let [match-len 
                 (if (.equals (aget a1 i) (aget a2 j))
                   (unchecked-inc (aget prev j))
                   0)]
             (aset curr (unchecked-inc j) match-len)
             (if (> match-len max-len)
               match-len
               max-len)))
         curr prev])))) ;; <= swaps prev and curr for the next iteration

根据 Java 版本,prev并且被“重用” - 一种类似于此处描述curr的动态编程方法。但是,这样做需要在每次迭代时分配一个新向量,然后将其传递给下一次归约。

通过将其放置在外部prev并使其成为封闭对象的成员,他避免了在每次迭代中分配一个持久向量,而只是支付了装箱 a 的成本(甚至可能没有)。currareduce^:unsynchronized-mutableIFnlong

所以“丑陋”的把戏不在他的 Clojure 代码的先前迭代中,而是在 Java 版本中。

于 2013-07-12T14:37:45.880 回答
0

正如 Chas 提到的,带有原始提示的循环是有问题的。当您提供提示时,Clojure 会尝试保持 int 未装箱,但当它无法遵守提示时,它将(在大多数情况下)静默失败。因此,他通过创建具有可变字段的 deftype 并将其设置在循环内来强迫它发生。这是一个丑陋的 hack,但绕过了编译器的一些限制。

于 2013-07-11T13:52:02.903 回答