I just started learning Clojure. One of the first things I noticed is that there are no loops. That's OK, I can recur. So let's look at this function (from Practical Clojure):
(defn addup
"Adds up numbers from 1 to n"
([n] (addup n 0 0))
([n i sum]
(if (< n i)
sum
(recur n (+ 1 i) (+ i sum)))))
To achieve the same function in Javascript, we use a loop like so:
function addup (n) {
var sum = 0;
for(var i = n; i > 0; i) {
sum += i;
}
return sum;
}
When timed, the results look like:
input size: 10,000,000
clojure: 818 ms
nodejs: 160 ms
input size: 55,000,000
clojure: 4051 ms
nodejs: 754 ms
input size: 100,000,000
clojure: 7390 ms
nodejs: 1351 ms
I then proceeded to try the classic fib (after reading this):
in clojure:
(defn fib
"Fib"
[n]
(if (<= n 1) 1
(+ (fib ( n 1)) (fib ( n 2)))))
in js:
function fib (n) {
if (n <= 1) return 1;
return fib(n1) + fib(n2);
}
Again, the performance has quite some difference.
fib of 39
clojure: 9092 ms
nodejs: 3484 ms
fib of 40
clojure: 14728 ms
nodejs: 5615 ms
fib of 41
clojure: 23611 ms
nodejs: 9079 ms
Note I'm using (time (fib 40)) in clojure so it's ignoring the bootup time for JVM. These are ran on a MacBook Air (1.86 Ghz Intel Core 2 Duo).
So what's causing Clojure to be slow here? And why do people say that "Clojure is fast"?
Thanks in advance and Please, no flame wars.
(set! *uncheckedmath* true)
(defn addup ^long [^long n]
(loop [n n i 0 sum 0]
(if (< n i)
sum
(recur n (inc i) (+ i sum)))))
(defn fib ^long [^long n]
(if (<= n 1) 1
(+ (fib (dec n)) (fib ( n 2)))))
(comment
;; ~130ms
(dotimes [_ 10]
(time
(addup 1e8)))
;; ~1180ms
(dotimes [_ 10]
(time
(fib 41)))
)
All numbers from 2.66ghz i7 Macbook Pro OS X 10.7 JDK 7 64bit
As you can see Node.js is trounced. This is with 1.3.0 alphas, but you can achieve the same thing in 1.2.0 if you know what you're doing.
On my machine Node.js 0.4.8 for addup 1e8 was ~990ms, for fib 41 ~7600ms.
Node.js  Clojure

addup 990ms  130ms

fib(41) 7600ms  1180ms
I'd actually expect Clojure to be significantly faster than Javascript if you optimise your code for performance.
Clojure will statically compile to fairly optimised Java bytecode whenever you give enough static type information (i.e. type hints or casts to primitive types). So in theory at least, you ought to be able to get pretty close to pure Java speed, which is itself pretty close to native code performance.
So let's prove it!
In this case, you have several issues that are causing the Clojure code to run slow:
n
is boxed, which means that the <= function call cannot be optimised to use primitive arithmetic. You can avoid this by casting n into a long primitive with a local let.(time (somefunction))
is also an unreliable way to benchmark in Clojure because it won't necessarily allow JIT compilation optimisations to kick in. You often need to run (somefunction) a few times first so that the JIT has a chance to do its work.My suggestion for the optimised Clojure version of addup would therefore be something more like:
(defn addup
"Adds up numbers from 1 to n"
[n]
(let [n2 (long n)] ; unbox loop limit
(loop [i (long 1) ; use "loop" for primitives
acc (long 0)] ; cast to primitive
(if (<= i n2) ; use unboxed loop limit
(recur (uncheckedinc i) (uncheckedadd acc i)) ; use unchecked maths
acc))))
And a better way to time it is as follows (to allow JIT compilation to happen):
(defn f [] (addup 10000000))
(do
(dotimes [i 10] (f))
(time (f)))
If I do the above, I get 6 ms for the Clojure solution in Clojure 1.2. Which is something like 1520x faster than the Node.js code and maybe 80100x faster than your original Clojure version.
Incidentally, this is also about as fast as I can get this loop to go in pure Java so I doubt that it would be possible to improve this much in any JVM language. It also puts us at about 2 machine cycles per iteration... so it's probably not far from native machine code speed either!
(sorry not able to benchmark against Node.js on my machine, but it's a 3.3 GHz core i7 980X for anyone interested)