Some performance problems:
- The
(range 2 20)
call is creating a new lazy list of numbers for every increment ofi
. This is expensive, and is causing lots of unnecessary GC. - You are doing a lot of boxing by passing through function calls. Even the
(iterate inc 1)
is doing boxing / unboxing at every increment. - You are traversing a sequence of divisors. This is slower than a straight iterative loop
mod
is actually not a very well optimised function in Clojure at present. You are much better off usingrem
You can solve the first problem by using a let
statement to define the range just once:
(time (let [rng (range 2 20)]
(prn (some (fn [^long i] (has-all-divisors rng i)) (iterate inc 1)))))
=> "Elapsed time: 48863.801522 msecs"
You can solve the second problem with loop/recur:
(time (let [rng (range 2 20)
f (fn [^long i] (has-all-divisors rng i))]
(prn (loop [i 1]
(if (f i)
i
(recur (inc i)))))))
=> "Elapsed time: 32757.594957 msecs"
You can solve the third problem by using an iterative loop over the possible divisors:
(defn has-all-divisors [^long num]
(loop [d (long 2)]
(if (zero? (mod num d))
(if (>= d 20) true (recur (inc d)))
false)))
(time (prn (loop [i (long 1)] (if (has-all-divisors i) i (recur (inc i))))))
=> "Elapsed time: 13369.525651 msecs"
You can solve the final problem using rem
(defn has-all-divisors [^long num]
(loop [d (long 2)]
(if (== 0 (rem num d))
(if (>= d 20) true (recur (inc d)))
false)))
(time (prn (loop [i (long 1)] (if (has-all-divisors i) i (recur (inc i))))))
=> "Elapsed time: 2423.195407 msecs"
As you can see, it is now competitive with the Java version.
In general, you can usually make Clojure almost as fast as Java with a bit of effort. The main tricks are usually:
- Avoid lazy functional features. They are nice, but add some overhead which can be problematic in low-level computation-intensive code.
- Use primitive / unchecked maths
- Use loop/recur rather than sequences
- Ensure you are not doing any reflection on Java objects (i.e.
(set! *warn-on-reflection* true)
and eliminate all warnings you find)