8

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;
}

3 Answers 3

8

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))
Sign up to request clarification or add additional context in comments.

1 Comment

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.
5

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 were boxed.

4 Comments

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.
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.
Without type hinting, how would you deal with mix-type collections?
@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.
3

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/

3 Comments

it would be really nice to map functions on primitives.
static functions are a prerequisite for using primatives as arguments or return types.
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.