I am doing some (naive and trivial) performance tests before deciding whether and how to use Clojure for some performance critical number cruching and I wanted help understanding the behaviour.
I am defining an array inside a function, setting the contents to be 1 and then summing them up (by areducing) them (I chose 1 instead of a random number for consistency, obviously the contents will be different otherwise it would all reduce to (n) :)). If I 'let' the array then it is factors of 10 faster than if I def the array.
[code]
(ns inc
(:gen-class))
(defn- inc-atom [n]
(def x (atom 0))
(dotimes [n n] (swap! x inc))
@x)
(defn- array-let [n]
(let [a (int-array n)]
(dotimes [n n] (aset-int a n 1))
(areduce a i ret 0
(+ ret (aget a i)))))
(defn- array-def [n]
(def a (int-array n))
(dotimes [n n] (aset-int a n 1))
(areduce a i ret 0
(+ ret (aget a i))))
(defn- run-test [subject n]
(time (do (def x (subject n)) (println x))))
(defn -main [& args]
(let [n 1000000]
(println "inc atom")
(run-test inc-atom n)
(println "array with let")
(run-test array-let n)
(println "array with def")
(run-test array-def n))
)
[/code]
Interestingly, if I refactored an 'execute-on-array' def which array-let and array-def delegated to then they had the same performance which seems to imply it is about scoping, but the array in both array-let and array-def have exactly the same scope... Setting the autoboxing warning to true didn't point out anything either.
The output (from my VM, so a bit slow):
[code]
inc atom
1000000
"Elapsed time: 213.214118 msecs"
array with let
1000000
"Elapsed time: 75.302602 msecs"
array with def
1000000
"Elapsed time: 12868.970203 msecs"
[/code]
For comparison, the following java code:
[code]
package perf;
public class Inc {
public static void main(String[] args) {
int n = 1000000;
int counter = 0;
long start = System.currentTimeMillis();
for (int i=0; i<n; i++) counter++;
long end = System.currentTimeMillis();
System.out.println ("Naive " + (end - start) + " ms, counter is " + counter);
counter = 0;
int[] arr = new int[n];
start = System.currentTimeMillis();
for (int i=0; i<arr.length; i++) arr[i]=1;
for (int i=0; i<arr.length; i++) counter = counter + arr[i];
end = System.currentTimeMillis();
System.out.println ("Array " + (end - start) + " ms, counter is " + counter);
}
}
[/code]
produces the (as expected, much faster) results :
[code]
Naive 3 ms, counter is 1000000
Array 6 ms, counter is 1000000
[/code]
I am not surprised that the atom/inc takes much longer than 3 ms, but I don't understand why the array solution is so much more expensive in Clojure?
On a related point - can anyone provide a faster implementation of summing up the contents of an array?