Java 昂贵算法的Clojure性能

Java 昂贵算法的Clojure性能,java,performance,clojure,Java,Performance,Clojure,我已经实现了一个算法来计算最长的连续公共子序列(不要与最长公共子序列混淆,尽管对于这个问题并不重要)。我需要从中获得最大的性能,因为我会经常这么说。为了比较性能,我在Clojure和Java中实现了相同的算法。Java版本的运行速度要快得多我的问题是,我是否可以对Clojure版本做些什么来将其加速到Java级别。 以下是Java代码: public static int lcs(String[] a1, String[] a2) { if (a1 == null || a2 == nu

我已经实现了一个算法来计算最长的连续公共子序列(不要与最长公共子序列混淆,尽管对于这个问题并不重要)。我需要从中获得最大的性能,因为我会经常这么说。为了比较性能,我在Clojure和Java中实现了相同的算法。Java版本的运行速度要快得多我的问题是,我是否可以对Clojure版本做些什么来将其加速到Java级别。

以下是Java代码:

public static int lcs(String[] a1, String[] a2) {
    if (a1 == null || a2 == null) {
        return 0;
    }

    int matchLen = 0;
    int maxLen = 0;

    int a1Len = a1.length;
    int a2Len = a2.length;
    int[] prev = new int[a2Len + 1]; // holds data from previous iteration of inner for loop
    int[] curr = new int[a2Len + 1]; // used for the 'current' iteration of inner for loop

    for (int i = 0; i < a1Len; ++i) {
        for (int j = 0; j < a2Len; ++j) {
            if (a1[i].equals(a2[j])) {
                matchLen = prev[j] + 1; // curr and prev are padded by 1 to allow for this assignment when j=0
            }
            else {
                matchLen = 0;
            }
            curr[j+1] = matchLen;

            if (matchLen > maxLen) {
                maxLen = matchLen;
            }
        }

        int[] swap = prev;
        prev = curr;
        curr = swap;
    }

    return maxLen;
}
爪哇:

Clojure:

(time (lcs a1 a2))
"Elapsed time: 19863.633 msecs"
Clojure速度很快,但仍然比Java慢一个数量级。我能做些什么来弥补这个差距吗?或者我把它最大化了,一个数量级是“最小Clojure开销”

正如您所看到的,我已经在使用循环的“低级”构造,我正在使用本机Java数组,并且我已经暗示了类型参数以避免反射


有一些算法优化是可能的,但我现在不想去那里。我很好奇我能获得多接近Java的性能。如果我不能缩小差距,我就使用Java代码。本项目的其余部分在Clojure中,但有时可能需要使用Java来提高性能。

以下是一些改进:

  • 别致的类型暗示没有任何好处,只需使用^objects即可
  • 我相信aset int已经被弃用了——只是普通的旧aget速度更快,总体上看起来大约是3倍
  • 除此之外(以及上面提到的关于复发的长类型提示),我看不到任何明显的改进方法

    (defn lcs
      [^objects a1 ^objects a2]
      (let [a1-len (alength a1)
            a2-len (alength a2)
            prev (int-array (inc a2-len))
            curr (int-array (inc a2-len))]
        (loop [i 0 max-len 0 prev prev curr curr]
          (if (< i a1-len)
            (recur (inc i)
                   (long (loop [j 0 max-len max-len]
                     (if (< j a2-len)
                       (if (= (aget a1 i) (aget a2 j))
                         (let [match-len (inc (aget prev j))]
                           (do
                             (aset curr (inc j) match-len)
                             (recur (inc j) (max max-len match-len))))
                         (do
                           (aset curr (inc j) 0)
                           (recur (inc j) max-len)))
                       max-len)))
                   curr
                   prev)
            max-len))))
    #'user/lcs
    user> (time (lcs a1 a2))
    "Elapsed time: 3862.211 msecs"
    
    (定义lcs)
    [^对象a1^对象a2]
    (让[a1长度(a1长度)
    a2长度(长度a2)
    上一个(整数数组(包括a2 len))
    curr(整数数组(包括a2 len))]
    (循环[i 0最大长度0上一次电流]
    (如果((时间(lcs a1 a2))
    “运行时间:3862.211毫秒”
    
    编辑:在第一个版本的基础上添加了一个更快更丑的版本

    以下是我的看法:

    (defn my-lcs [^objects a1 ^objects a2]
      (first
        (let [n (inc (alength a1))]
          (areduce a1 i 
            [max-len ^ints prev ^ints curr] [0 (int-array n) (int-array n)]
            [(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]))))
    
    与您的主要区别是:
    a[gs]et
    vs
    a[gs]et int
    ,使用
    未选中-
    ops(隐式地通过
    areduce
    ),使用向量作为返回值(以及“交换”机制),并且在内部循环之前将max len强制为原语(原语值循环有问题,比1.5RC2稍微少一些,但支持还不完善,但是
    *反射时警告*
    不是无声的)

    我切换到
    .equals
    ,而不是
    =
    ,以避免Clojure等效中的逻辑

    编辑:让我们重新开始并恢复阵列交换技巧:

    (deftype F [^:unsynchronized-mutable ^ints curr
                ^:unsynchronized-mutable ^ints prev]
      clojure.lang.IFn
      (invoke [_ a1 a2]
        (let [^objects a1 a1
              ^objects a2 a2]
          (areduce a1 i max-len 0
            (let [m (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) (unchecked-int match-len))
                        (if (> match-len max-len)
                          match-len
                          max-len)))
                  bak curr]
              (set! curr prev)
              (set! prev bak)
              m)))))
    
    (defn my-lcs2 [^objects a1 a2]
      (let [n (inc (alength a1))
            f (F. (int-array n) (int-array n))]
        (f a1 a2)))
    

    在我的框中,速度快了30%。

    首先,将(设置!*warn On reflection*true)放在Clojure实现名称空间的顶部,然后重新加载,注意所有警告,然后解决它们。其次,理想情况下,用于基准测试。接下来,在分析器中检查它……我们开始“自动装箱循环arg:max len”…谢谢,Hendekagon!所有好的提示。我去掉了自动装箱警告(通过使用(int)包装对内部循环的调用),但这并没有提高性能。然后我将对inc的所有调用都改为unchecked-inc。这也没有任何影响。我想应该是(长)不是int…这并不能回答您的问题,但是像这样一个天真的惯用Clojure实现可能会更慢,但它更容易推理和处理任何类型,并且它可以在结束和开始之间的重叠中找到序列(编写起来比Java简单10亿倍)。如果有办法让这段代码运行得和Java一样快就好了!aset确实快了3倍!谢谢你。在我的机器上,Java的运行速度是~1.5毫秒,Clojure的运行速度是~6毫秒。不错。我也尝试了(long)而不是(int),但没有什么不同。还将参数typehint改为^objects而不是^Java.lang.String对性能没有影响。这是令人惊讶的。你对Clojure还不知道的^objects暗示了什么?用Math/max回复max似乎不会产生显著的改进。还可以将其替换为(if(>match len max len)match len max len)似乎也没有任何影响。谢谢。我分别测试了每个修改。如上所述,aset确实比aset-int快约3倍。因此,它的运行时间降低到Java的4倍。未检查的数学没有任何明显的差异。但是使用.equals而不是=会带来另外3-4倍的改进!所有的优化到目前为止,我的Java和Clojure的性能分别为~1.5秒和~1.9秒。一点也不差!您的版本运行速度非常接近Java ~1.9秒和~1.5秒,但正如我上面所说的,我相信性能的提高来自于切换到aset和.equals。我通过切换到aset和.equals,并且没有其他修改,可以获得与您的版本相同的性能。您呢可能只是使用
    =
    而不是
    .equals
    *未检查的数学*true
    我认为您不需要
    未检查的-*
    函数。@cgrand-还感谢您提供了一个使用areduce的替代实现。它感觉比循环更接近,尽管两者都不太糟糕。
    (defn lcs
      [^objects a1 ^objects a2]
      (let [a1-len (alength a1)
            a2-len (alength a2)
            prev (int-array (inc a2-len))
            curr (int-array (inc a2-len))]
        (loop [i 0 max-len 0 prev prev curr curr]
          (if (< i a1-len)
            (recur (inc i)
                   (long (loop [j 0 max-len max-len]
                     (if (< j a2-len)
                       (if (= (aget a1 i) (aget a2 j))
                         (let [match-len (inc (aget prev j))]
                           (do
                             (aset curr (inc j) match-len)
                             (recur (inc j) (max max-len match-len))))
                         (do
                           (aset curr (inc j) 0)
                           (recur (inc j) max-len)))
                       max-len)))
                   curr
                   prev)
            max-len))))
    #'user/lcs
    user> (time (lcs a1 a2))
    "Elapsed time: 3862.211 msecs"
    
    (defn my-lcs [^objects a1 ^objects a2]
      (first
        (let [n (inc (alength a1))]
          (areduce a1 i 
            [max-len ^ints prev ^ints curr] [0 (int-array n) (int-array n)]
            [(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]))))
    
    (deftype F [^:unsynchronized-mutable ^ints curr
                ^:unsynchronized-mutable ^ints prev]
      clojure.lang.IFn
      (invoke [_ a1 a2]
        (let [^objects a1 a1
              ^objects a2 a2]
          (areduce a1 i max-len 0
            (let [m (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) (unchecked-int match-len))
                        (if (> match-len max-len)
                          match-len
                          max-len)))
                  bak curr]
              (set! curr prev)
              (set! prev bak)
              m)))))
    
    (defn my-lcs2 [^objects a1 a2]
      (let [n (inc (alength a1))
            f (F. (int-array n) (int-array n))]
        (f a1 a2)))