On Tue, May 10, 2011 at 2:21 PM, Carl Cotner <carl.cot...@gmail.com> wrote:
> Hi,
>
> Is it possible to make Clojure run the following toy program faster
> (without changing the algorithm)?
>
> (defn primeQ [n] (primes 2 (/ n 2) n))
>
> (defn divQ [d n] (= (mod n d) 0))
>
> (defn primes [d2 t2 n2]
>  (loop [d (int d2) t (int t2) n (int n2)]
>    (if (divQ d n) false
>        (if (> d t) true
>            (recur (inc d) t n)))))
>
> Running this on my machine:
>
> user> (time (primeQ 71378569))
> "Elapsed time: 11047.721488 msecs"
>
> is roughly 13 times slower than running the comparable Java program.
> It seems like one should be able to do much better for such a
> straightforward program. Is there anything I can do to make this
> program faster?

Several things, though at the expense of idiomaticity of the code in many cases.

1: Algorithmic smarts. Division is expensive. Replace divQ's
implementation in terms of mod with the algorithm binary GCD and it
will probably be faster. Likewise the / n 2 in primeQ can be changed
to a bit-shift-right.

2: Algorithmic smarts. There are more sophisticated algorithms for
checking for primality, for example only checking for divisibility by
primes smaller than or equal to the square root of the number rather
than every number from 2 to half the number.

3: Technical speedups. Avoid function calls inside inner loops, which
cause boxing and require var dereferencing. Fold divQ into primes,
since it's only used once in there.

4: Technical speedups. Use unchecked primitive math throughout, if you
know the range of a Java long will not be exceeded. With Clojure 1.2,
use unchecked-inc and such (also in the binary GCD that will now be in
primes in place of the divQ call) and make sure the primitives stay
unboxed -- in particular, use loop/recur in primes (which means you
may as well fold primes into primeQ as well) to avoid boxing at the
end of each iteration.

5: Technical speedups. Make sure to use java -server to run it.

6: Benchmarking accuracy. Don't include the JIT compilation, the
overhead of calling the primeQ function and of the benchmarking
itself, or, God forbid, the JVM startup time; do a few thousand
repetitions of the whole thing with a small (e.g. four-digit) prime to
get the JITting done and then do a big number several times and
average the results, using a timing macro of some sort from inside the
running Clojure session to avoid counting the JVM startup time. The
timing/call overhead will be lost in the noise if the number tested in
the timed runs is big enough.

By the way, you can (and generally should) name your Clojure
predicates with a question-mark, e.g. div? instead of divQ or div-p. I
take it you're used to another Lisp?

If you're trying to do an apples-to-apples comparison of Java and
Clojure, though, rather than make the fastest Clojure primality
tester, then you will want to ignore points 1 and 2 above unless you
make the same algorithmic improvements to both versions and you will
want the timing loop to be internal (rather than a shell thing
wrapping the JVM invocation) in both cases (Clojure has more
dependencies, so takes a bit longer to start up the JVM, but this
won't matter in any sane situation, whereas math speed will).

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to