Interpreting a benchmark in C, Clojure, Python, Ruby, Scala and others [closed] Interpreting a benchmark in C, Clojure, Python, Ruby, Scala and others [closed] python python

Interpreting a benchmark in C, Clojure, Python, Ruby, Scala and others [closed]


Rough answers:

  1. Scala's static typing is helping it quite a bit here - this means that it uses the JVM pretty efficiently without too much extra effort.
  2. I'm not exactly sure on the Ruby/Python difference, but I suspect that (2...n).all? in the function is-prime? is likely to be quite well optimised in Ruby (EDIT: sounds like this is indeed the case, see Julian's answer for more detail...)
  3. Ruby 1.9.3 is just much better optimised
  4. Clojure code can certainly be accelerated a lot! While Clojure is dynamic by default, you can use type hints, primitive maths etc. to get close to Scala / pure Java speed in many cases when you need to.

Most important optimisation in the Clojure code would be to use typed primitive maths within is-prime?, something like:

(set! *unchecked-math* true) ;; at top of file to avoid using BigIntegers(defn ^:static is-prime? [^long n]  (loop [i (long 2)]     (if (zero? (mod n i))      false      (if (>= (inc i) n) true (recur (inc i))))))

With this improvement, I get Clojure completing 10k in 0.635 secs (i.e. the second fastest on your list, beating Scala)

P.S. note that you have printing code inside your benchmark in some cases - not a good idea as it will distort the results, especially if using a function like print for the first time causes initialisation of IO subsystems or something like that!


Here's a fast Clojure version, using the same basic algorithms:

(set! *unchecked-math* true)(defn is-prime? [^long n]  (loop [i 2]    (if (zero? (unchecked-remainder-int n i))      false      (if (>= (inc i) n)        true        (recur (inc i))))))(defn sexy-primes [m]  (for [x (range 11 (inc m))        :when (and (is-prime? x) (is-prime? (- x 6)))]    [(- x 6) x]))

It runs about 20x faster than your original on my machine. And here's a version that leverages the new reducers library in 1.5 (requires Java 7 or JSR 166):

(require '[clojure.core.reducers :as r]) ;'(defn sexy-primes [m]  (->> (vec (range 11 (inc m)))       (r/filter #(and (is-prime? %) (is-prime? (- % 6))))       (r/map #(list (- % 6) %))       (r/fold (fn ([] []) ([a b] (into a b))) conj)))

This runs about 40x faster than your original. On my machine, that's 100k in 1.5 seconds.


I'll answer just #2, since it's the only one I've got anything remotely intelligent to say, but for your Python code, you're creating an intermediate list in is_prime, whereas you're using .map in your all in Ruby which is just iterating.

If you change your is_prime to:

def is_prime(n):    return all((n%j > 0) for j in range(2, n))

they're on par.

I could optimize the Python further, but my Ruby isn't good enough to know when I've given more of an advantage (e.g., using xrange makes Python win on my machine, but I don't remember if the Ruby range you used creates an entire range in memory or not).

EDIT: Without being too silly, making the Python code look like:

import timedef is_prime(n):    return all(n % j for j in xrange(2, n))def primes_below(x):    return [(j-6, j) for j in xrange(9, x + 1) if is_prime(j) and is_prime(j-6)]a = int(round(time.time() * 1000))print(primes_below(10*1000))b = int(round(time.time() * 1000))print(str((b-a)) + " mils")

which doesn't change much more, puts it at 1.5s for me, and, with being extra silly, running it with PyPy puts it at .3s for 10K, and 21s for 100K.