tags:

views:

187

answers:

3

Can this Java code be translated to Clojure code that's as fast or nearly as fast?

I've been able to get simpler functions like adding two arrays to run at reasonable speeds with type hinting, but I couldn't get Clojure to do what the functions below do at all in a reasonable amount of time using either Java interop or Incanter matrices and using either functional or imperative styles.

Am I missing something about type hinting or is it just best to do this kind of thing in 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;
}
+4  A: 

starting with the clojure 1.3 branch currently on github you can use primitives as arguments to functions and returns from functions. You also will no longer have to type hint number primitives. It should really make hinting this type of code a lot faster and look a lot more elegant.

In type hinting this you could be running up against the fact that (<= clojure 1.2) all function arguments where boxed.

Arthur Ulfeldt
What does that mean exactly that the function arguments are boxed? I was able to get clojure code that adds two 2d java arrays to run pretty close to native speeds. As I understand it now the function pulls in numbers from java, adds them in clojure, and then pushes the result back into a java array, and the type hints there for the clojure to java interactions. But my clojure versions of the java code do the same thing only there are more operations in clojure before I push things back to java. But I don't understand why the additional operations would make things so much slower.
Ranjit
When i say function call, I am referring to calling a clojure function. if you run (my-function 42 :-P) the number 42 is an Integer class instance and not a primitive int, even if you type hint it. also the return type of a clojure-function call is always an Object and never a primitive (prior to clojure 1.3) The transition to clojure 1.3 can drop one zero from the running time of some heavily numerical functions.
Arthur Ulfeldt
Without type hinting, how would you deal with mix-type collections?
Hamish Grubijan
@Hamish Grubijan: the mixed type collections store everything as type Object and box any primitives. if you passed a collection to a function you would be calling the non-static version of the function and not passing it primitives. only ^:static functions can take primitives. you don't have to worry about manually calling the static version of a function. its automatic.
Arthur Ulfeldt
+3  A: 

The other piece that will help (also in 1.3) is static function linking, which will make some function calls as fast as method calls (this is also described in the link Arthur posted).

It'll still be difficult to write this code in a truly idiomatic way (ie, using the "map" higher order function) for now at full java performance, since higher order functions won't be able to use static linking, but (shameless plug warning) this is something Rich Hickey wants to rectify:

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

tvachon
it would be really nice to map functions on primitives.
Arthur Ulfeldt
static functions are a prerequisite for using primatives as arguments or return types.
Arthur Ulfeldt
the way they're currently being implemented, that's true. Rich drew a distinction during the SF meetup, noting that at the JVM level, they aren't strictly coupled - it sounds like the primitive support he's implementing may at some point not be tightly coupled with static functions.
tvachon
+5  A: 

Try the following in clojure 1.3 (master branch):

(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))
Alex Taggart
I tried creating an array: (def A (into-array (map double-array (map #(repeat L %1) (range L))))) , and then your grad2 function: (time (grad2 A 1 1)) but it just aborts and I don't see any stack trace for some reason.
Ranjit