Interpreting a benchmark in C, Clojure, Python, Ruby, Scala and others [closed]
Rough answers:
- 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.
- I'm not exactly sure on the Ruby/Python difference, but I suspect that
(2...n).all?
in the functionis-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...) - Ruby 1.9.3 is just much better optimised
- 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.