This post was inspired by this stackoverflow question (link will open in a new page ) which uses the computing time of a nice math problem; computing the sexy primes numbers in order to measure the performance of each programming language. While not a world class benchmark, I found the benchmark attractive, simple and meaningful. But the post is *outdated* now (about three years plus) and I have one take on the test itself that it actually takes the time of printing in the console as part of language's performance; which skews testing results significantly since various REPL environments or editors behave in different capacities when printing lots of data to the console which I will be showing too.
About The Test
Hence, I've altered the code to measure the performance of finishing and writing the output to a file. I've used the same method of writing to a file for the three languages of interest: Ruby, Clojure and Scala. Clojure and Scala use the same underlying Java file writing functionality and Ruby uses in my code the same style or type of writing files. This eliminates this variance in console performance on one side and it is meaningful to count the performance of writing a file as part of the metric since it is a standard valid I/O operation which constitutes an intrinsic feature of the language.
About The Test
Hence, I've altered the code to measure the performance of finishing and writing the output to a file. I've used the same method of writing to a file for the three languages of interest: Ruby, Clojure and Scala. Clojure and Scala use the same underlying Java file writing functionality and Ruby uses in my code the same style or type of writing files. This eliminates this variance in console performance on one side and it is meaningful to count the performance of writing a file as part of the metric since it is a standard valid I/O operation which constitutes an intrinsic feature of the language.
And to what extent various console tests can skew test results?
The following shows test results for Clojure done in REPL and again performed inside LightTable editor:
sexy prime numbers 10k 30k 60k 100k Clojure 1.7
optimized(repl)100 ms 721 ms 2740 ms 6887 ms Clojure 1.7
optimized
(LightTable editor)80 ms 542 ms 1918 ms 5391 ms
You can see that the difference is actually huge -sometimes ~25%-. So which one should I take to compare against the other language(s), and which editor the other language should use. You get the picture.
The Problem
Sexy primes is simply any finding of two prime numbers that differ from each other by 6. So these pairs (5,11), (7,13), (11,17), (13,19), (17,23 are sexy primes. My goal here is to measure the performance of what I think the three most famous languages right now are: Ruby, Clojure and Scala.
Platform
Hardware : a MacBook Pro, i7, 16GB RAM
Operating system: OSX Yosemite 10.10.5
Languages : Ruby 2.2.2p95 vs Clojure 1.7 vs Scala 2.11.7
Java :
Java(TM)
SE Runtime Environment (build 1.8.0_60-b27)
Source Code
Note that when printing in all three languages I type cast to a string before printing to a file.
Ruby:def is_prime?(n)
(2...n).all?{|m| n%m != 0 }
end
def sexy_primes(x)
(9..x).map do |i|
[i-6, i]
end.select do |j|
j.all?{|j| is_prime? j}
end
end
output_file = "ruby-sexy-primes-test-no-100.txt"
a = Time.now
File.open(output_file, 'wb') do |f|
f.print( sexy_primes(100*1000))
end
b = Time.now
puts "#{(b-a)*1000} mils"
Clojure:(ns user (:import (java.io BufferedWriter FileWriter)))
(defn is-prime? [n]
(every? #(> (mod n %) 0) ]
(range 2 n)))
(defn sexy-primes [m]
(for [x (range 11 (inc m))
:let [z (list (- x 6) x)]
:when (every? #(is-prime? %) z)]
z))
(defn spit-it [file-name data]
(with-open [wtr (BufferedWriter. (FileWriter. file-name))]
(.write wtr data)))
(let [a (System/currentTimeMillis)]
(spit-it "clojure-sexy-primes-test-no-100.txt" (pr-str (sexy-primes (* 100 1000))))
(let [b (System/currentTimeMillis)] (println (- b a) "mils")))
Clojure optimized is-prime?
:(defn ^:static is-prime? [^long n]
(loop [i (long 2)]
(if (= (rem n i) 0)
false
(if (>= (inc i) n) true
(recur (inc i))))))
Scala:
import java.io._
def isPrime
(n: Int) =
(2 until n) forall { n % _ != 0 }
def sexyPrimes
(n: Int) =
(11 to n) map { i => List(i-6, i) } filter { _ forall(isPrime(_)) }
val writer = new PrintWriter(new File("scala-sexy-primes-test-100.txt"))
val a = System.currentTimeMillis(); writer.write(sexyPrimes(100*1000).toString); writer.close(); val b = System.currentTimeMillis()
println((b-a).toString + " mils")
Scala optimized
isPrime
(the same idea like in Clojure optimization): import scala.annotation.tailrec
// Not required, but will warn if
// optimization doesn't work
@tailrec
def isPrime(n: Int, i: Int = 2): Boolean =
if (i == n) true
else if (n % i != 0) isPrime(n, i + 1)
else false
Results
Test
results writing to a file:
- sexy prime numbers
10k 30k 60k 100k Clojure 1.7 470 ms 3384 ms 12866 ms 32505 ms Clojure 1.7
optimized67 ms 564 ms 1834 ms 5149 ms Scala 2.11.7 66 ms 474 ms 1747 ms 4556 ms Scala 2.11.7
optimized22 ms 144 ms 572 ms 1459 ms Ruby 2.2.2p95 593.66 ms 4436.85 ms 16443.66 ms 43520.99 ms
The popularity of Clojure in scientific computing and AI is due to the fact that Clojure is fitting for programming symbolic systems, mathematical programming along with other needs while utilizing external optimized C/Fortran libraries like LAPAC, BLAS and alike which both languages Scala and Clojure would need in these scenarios anyways. Thus you get the speed along with Clojures innate AI abilities (for text processing and symbolic systems) which is not a trivial thing to ignore. On the other hand Type Inference in Scala might be of help here; something I need to take a close look at.
Ruby's results are shocking to say the least and slower than Ruby 1.9.3. I don't know what went wrong there. This needs further investigation.