Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How, if at all, can I improve my numerical performance in Clojure?

Here is a short Scala program which computes a poor approximation of Euler's number by generating a bunch of random numbers:

package example

import scala.util.Random

object ApproxE extends App {

   def calc = {
     var S = 0.0
     for {i <- 1 to 100000000} {
       var x = 0.0
       var t = 0
       while (x <= 1.0) {
         x += Random.nextDouble
         t += 1
       }
       S += t
     }
     S / 100000000
  }

  val st = System.currentTimeMillis()
  val e = calc
  val ed = System.currentTimeMillis()

  println(e)
  println(ed - st)

}

Running on my laptop, it does its computation in about 7 seconds.

The following are two equivalent Clojure functions. On returns in 48 seconds, and the other in 230 seconds.

Is it possible, in pure Clojure, to write an equivalent program with performance comparable to that achievable in a Java or Scala?

48s:

(defn calc []
    (loop [i (int 0)
           S (double 0.0)]
        (if (= i 100000000)
            (/ S 100000000)
            (let [rs (repeatedly rand)
                  ps (reductions + rs)
                  <=1 #(<= % 1)]
              (->> ps
                (take-while <=1)
                (count)
                (inc)
                (+ S)
                (recur (inc i)))))))

230s:

(defn calc2 []
    (with-local-vars [S 0.0]
        (dotimes [i 100000000]
            (with-local-vars [x (double 0)
                              t (int 0)]
                (while (<= @x 1)
                    (var-set x (+ @x (rand)))
                    (var-set t (inc @t)))
                (var-set S (+ @S @t))))
        (/ @S 100000000)))
like image 202
mac01021 Avatar asked Mar 06 '23 07:03

mac01021


1 Answers

Both lazy data structures and local vars introduce an overhead due to allocations (though you can still make the variant with the vars a bit faster by moving the allocations for x and t out of the loop), name resolution (in case of vars) and method calling.

A word-to-word translation of the Scala code would use local bindings (using let and loop), which are equivalent to Java's local variables:

(defn calc-loops []
  (loop [i (int 0)
         S (double 0.0)]
    (if (= i 100000000)
        (/ S 100000000)
        (recur
         (inc i)
         (loop [t 0 x 0.0]
           (if (<= x 1.0)
             (recur (inc t) (+ x (rand)))
             (+ S t)))))))

(time (calc))       ;; => "Elapsed time: 56255.692677 msecs"
(time (calc-loops)) ;; => "Elapsed time: 8800.746127 msecs"
like image 177
Aleph Aleph Avatar answered Apr 28 '23 05:04

Aleph Aleph