Tuesday, May 06, 2008

Dynamic Language Performance

A couple of days ago, I read Charlie's post explaining the performance boost seen in Groovy 1.6. Reading stuff like this always leaves me with a great feeling. Not only do you learn something, but it makes other things make more sense. It brings order to chaos, or something like that. Around the same time I read that, I was working a new article about Grails, so the Groovy angle was particularly interesting. I love benchmarks, so it was time to have some fun.

I wrote a Groovy version of the same Ruby code I had used to benchmark JRuby. This was an extremely straightforward port. I was amazed at just how similar Groovy's syntax is to Ruby. Here is the code:


def expo(n,p){
def r = n % p
def exp = 0
def div = p
while (r == 0){
exp += 1
div *= p
r = n % div
}
return exp
}

def factor(n){
def factors = new java.util.HashMap<Integer,Integer>()
def s = n * 0.5
def p = (2..s).toArray()
p.each{
if (it) {
def r = expo(n,it)
if (r){
factors[it] = r
}
def val = it*2
while (val <= s){
p[val -2] = null
val += it
}
}
}
return factors
}

def numDivisors(n){
def total = 1
factor(n).values().each{
total *= (it+1)
}
return total
}

def n = 2
def num = 1
def max = Integer.parseInt(this.args[0])
def Integer triangle = 0
while (num <= max){
triangle = n*(n+1) * 0.5
num = numDivisors(triangle)
n += 1
}
println(triangle)


Anyways, here is the chart.

There is definitely a performance boost for long running processes where JIT'ing can happen more easily in 1.6. It was not as dramatic as I thought it might be, but it is there. Of course this is just one silly benchmark that is heavy in integer math, so take that for what it's worth. 

I also compared Groovy and JRuby. This was also surprising: 



Pretty close! Groovy seems to start-up a little slower, but pulled ahead slightly on bigger tasks. Perhaps the apprentice has overtaken the master.

Also, just for kicks, I tried out Scala. Here is the code:


import scala.collection.mutable._

object Euler12{
def expo(n:int, p:int):int = {
var r = n % p
var exp = 0
var div = p
while (r == 0){
exp = exp + 1
r = n % div
div = div * p
}
if (exp == 0 ) 0 else (exp-1)
}

def factor(n:int):Map[int,int] = {
var factors = new HashMap[int,int]()
var s:int = (n/2) + 1
val p = (2 until s).toArray
p.foreach( (num) => {
if (num > 1){
val r = expo(n, num)
if (r > 0){
factors.put(num, r)
}
var i = num*2
while ((i-2) < p.length){
p(i - 2) = 0
i = i + num
}
}
})
return factors
}

def numDivisors(n:int):int = {
var total = 1
factor(n).values.foreach((num) => {
total = total * (num+1)
})
factor(n).values.foldLeft(1)((p,m) => {
p * (m+1)
})
}

def main(args:Array[String]) : Unit = {
val t = new java.util.Date()
var n = 1
var num = 1
val max = Integer.parseInt(args(0))
var triangle = 3
while (num <= max){
triangle = n*(n+1)/2
num = numDivisors(triangle)
n = n + 1
}
println(triangle)
}
}


This turned out to not be fair. Scala's performance is exactly on par with Java and thus blows away JRuby and Groovy. 


I guess that is what happens when you have a language written by a guy who once wrote javac... Actually I would guess this is mostly a function of the static typing in Scala. It certainly bodes well for initiatives to bring features of Scala, like (BGGA-style) closures and type inference, to Java. It seems possible to implement all of this with no impact on performance, even on a JVM that has not been made to support such features. 

No comments: