Clojure performance really bad on simple loop versus Java

Some performance problems:

  • The (range 2 20) call is creating a new lazy list of numbers for every increment of i. 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 using rem

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)

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)