Search code examples
clojuregen-class

Does Clojure inlines primitive operations efficiently?


Assuming I have the following Clojure code:

(defn foo ^double []
  (- 
    (* 123.31
     (+ 4 5 6 (Math/sin 34.2))
     123.31) 
    123))

Will gen-class produce byte code equivalent to compiling the following java code:

public static double foo(){
   return (123.31 * (4 + 5 + 6 + Math.sin(34.2)) * 123.31) - 123;
}

Or in other words can I user Clojure as a very convenient DSL to produce efficient dynamic byte code?

Edit:

Ok, I did some test to illustrate my issue:

Here is java version:

public class FooTest {

  public static double foo(double a, double b, double c){
    return (a * (b + c + (b*c) + Math.sin(a)) * Math.log(b)) - b;
  }

  public static long benchmark(){
    long start = System.currentTimeMillis();
    for (double i = 0; i < 100000000.0; i++) { // 100 mln
      double r = foo(i, i+1, i+2);
    }
    long end = System.currentTimeMillis();
    return (end-start);
  }

  public static void main(String[] args) {
    System.out.println("Time took: "+benchmark());
  }
}

This produces the output: Time took: 39200

The clojure 'equivalent':

(defn foo ^double 
  (^double [a b c]
  (- 
    (* a
     (+ b c (* b c) (Math/sin a))
     (Math/log b)) 
    b)))

(time
  (loop [i 0.0] 
    (when (< i 100000000)   
      (foo i (+ i 1) (+ i 2))
      (recur (inc i)))))

That produces: "Elapsed time: 121242.902 msecs"

Which is 3 times slower.

Now my rephrased question is: How can I structure/hint clojure code so it avoids functions calls in code which is effectively primitive maths operations?

Edit2:

I have changed the test so it uses unchecked primitive maths operators:

(defn foo ^double 
  (^double [a b c]
  (binding [*unchecked-math* true] 
    (- 
      (* a
       (+ b c (* b c) (Math/sin a))
       (Math/log b)) 
      b))))

"Elapsed time: 64386.187 msecs" So it is almost 2 times better but still 1.6 times the java version.


Solution

  • Ok, I finally got identical performance of Clojure to java. Three things needed change:

    1. Proper hinting of the function arguments (previously I hinted return value not arguments to the function)
    2. I moved binding out of the function body.
    3. Using unchecked math operations in the micro-benchmark helper as well

    The resulting code is:

    (binding [*unchecked-math* true]   
      (defn foo ^double [^double a ^double b ^double c]  
        (- 
          (* a
             (+ b c (* b c) (Math/sin a))
             (Math/log b)) 
          b)))
    
    (binding [*unchecked-math* true]   
      (time
        (loop [i (double 0.0)] 
          (when (< i 100000000)   
            (foo i (+ i 1) (+ i 2))
            (recur (inc i))))))