【问题标题】:Turning Java code into fast Clojure code将 Java 代码转换为快速的 Clojure 代码
【发布时间】:2011-09-19 18:10:19
【问题描述】:

这个 Java 代码能否被翻译成同样快或几乎同样快的 Clojure 代码?

我已经能够获得更简单的函数,例如添加两个数组以通过类型提示以合理的速度运行,但我无法让 Clojure 在合理的时间内使用任何一种 Java 来完成下面的函数interop 或 Incanter 矩阵,并使用函数式或命令式样式。

我是否遗漏了一些关于类型提示的内容,还是最好在 Java 中做这种事情?

static double[][] grad2_stencil= { {0,0,-1,0,0}, 
                             {0,0,16,0,0}, 
                             {-1,16,-60,16,-1}, 
                             {0,0,16,0,0}, 
                             {0,0,-1,0,0} };

public static double grad2(double[][] array, int x, int y){
    double temp=0;
    int L=array.length;
    for(int i=0; i<5; i++){
        for(int j=0; j<5; j++){
            temp+=array[((x+i-2)%L+L)%L][((y+j-2)%L+L)%L]*grad2_stencil[i][j];
        }
    }
    return temp/12.0;
}

public static double[][] grad2_field(double[][] arr){
    int L=arr.length;
    double[][] result=new double[L][L];

    for(int i=0; i<L; i++){
        for(int j=0; j<L; j++){
            result[i][j]=grad2(arr, i, j);
        }
    }

    return result;
}

【问题讨论】:

    标签: clojure


    【解决方案1】:

    在 clojure 1.3(主分支)中尝试以下操作:

    (def ^"[[D" grad2-stencil
      (into-array (Class/forName "[D")
        (map double-array 
          [[ 0   0  -1  0  0 ] 
           [ 0   0  16  0  0 ]
           [-1  16 -60 16 -1 ] 
           [ 0   0  16  0  0 ] 
           [ 0   0  -1  0  0 ]])))
    
    (defn ^:static idx ^long [^long x ^long i ^long L]
      (-> x
        (+ i)
        (- 2)
        (mod L)
        (+ L)
        (mod L)))
    
    (defn ^:static grad2 ^double [^doubles arr ^long x ^long y]
      (let [L (alength arr)
            temp (loop [i 0 j 0 temp 0.0]
                   (if (< i 5) 
                     (let [a (idx x i L)
                           b (idx y j L)
                           temp (double (* (aget arr a b) 
                                          (aget grad2-stencil i j)))]
                       (if (< j 4)
                         (recur i (inc j) temp)
                         (recur (inc i) 0 temp)))
                     temp))]
        (/ temp 12.0)))
    
    (defn ^:static grad2-field ^"[[D" [^"[[D" arr]
      (let [result (make-array Double/TYPE (alength arr) (alength arr))]
        (loop [i 0 j 0]
          (when (< i 5)
            (aset result (grad2 arr i j) i j)
            (if (< j 4)
              (recur i (inc j))
              (recur (inc i) 0))))
        result))
    

    【讨论】:

    • 我尝试创建一个数组: (def A (into-array (map double-array (map #(repeat L %1) (range L))))) ,然后是你的 grad2 函数: (time (grad2 A 1 1)) 但它只是中止了,由于某种原因我没有看到任何堆栈跟踪。
    【解决方案2】:

    从目前在 github 上的 clojure 1.3 分支开始,您可以 use primitives as arguments to functions 并从函数返回。您也将不再需要键入提示数字原语。它真的应该让提示这种类型的代码更快,看起来更优雅。

    在类型提示中,您可能会遇到这样一个事实,即 (函数参数都被装箱。

    【讨论】:

    • 函数参数被装箱是什么意思?我能够获得 clojure 代码,它添加了两个 2d java 数组以非常接近原生速度运行。据我所知,该函数从 java 中提取数字,将它们添加到 clojure 中,然后将结果推回 java 数组,并且类型提示那里的 clojure 到 java 交互。但是我的 clojure 版本的 java 代码做同样的事情,只是在我将事情推回 java 之前,clojure 中有更多的操作。但我不明白为什么额外的操作会让事情变得这么慢。
    • 当我说函数调用时,我指的是调用 clojure 函数。如果您运行 (my-function 42 :-P) 数字 42 是整数类实例而不是原始 int,即使您输入提示也是如此。同样,clojure 函数调用的返回类型始终是 Object 而不是原始类型(在 clojure 1.3 之前)转换到 clojure 1.3 可以从一些大量数值函数的运行时间中减少一个零。
    • 如果没有类型提示,你将如何处理混合类型的集合?
    • @Hamish Grubijan:混合类型集合将所有内容存储为 Object 类型并将任何原语装箱。如果你将一个集合传递给一个函数,你将调用该函数的非静态版本,而不是传递它的原语。只有 ^:static 函数可以采用原语。您不必担心手动调用函数的静态版本。它是自动的。
    【解决方案3】:

    另一个有帮助的部分(也在 1.3 中)是静态函数链接,这将使一些函数调用与方法调用一样快(这也在 Arthur 发布的链接中描述)。

    目前仍然很难以真正惯用的方式(即使用“map”高阶函数)以完全 java 性能编写此代码,因为高阶函数将无法使用静态链接,但是(无耻的插头警告)这是 Rich Hickey 想要纠正的问题:

    http://combinate.us/clojure/2010/09/27/clojure/

    【讨论】:

    • 在原语上映射函数会非常好。
    • 静态函数是使用原语作为参数或返回类型的先决条件。
    • 它们目前的实施方式,这是真的。 Rich 在 SF 聚会上做出了区分,并指出在 JVM 级别,它们不是严格耦合的——听起来他正在实现的原始支持在某些时候可能不会与静态函数紧密耦合。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-09-16
    • 2019-11-02
    • 2011-05-31
    • 2013-07-12
    • 1970-01-01
    • 1970-01-01
    • 2015-02-10
    相关资源
    最近更新 更多