While playing with different sort algorithms I was surprised that Groovy closures were performing really poorly. I couldn't find a good answer to this so far, so trying my luck here now ;) Why are Groovy closures so much slower than traditional methods?
Here is a simple example that shows the performance difference. It creates two lists with random numbers and sorts them in reverse order, measuring the sorting time. On my machine and for 10k elements it takes 270ms using a closure and only 50ms using the Comparator implementation.
The timings vary a bit based on the distribution of random numbers. Also I tried Groovy 1.7.4 and 1.8.0, seeing slightly better performance with the latter. But the overall picture stays the same: closures perform poorly.
What can I do to improve closure performance? Besides not using closures, of course ;) Am I missing something or should one not use closures in groovy if performance counts?
def numberCount = 10000
def random = new Random()
def unorderedList1 = (1..numberCount).collect{random.nextInt()}
def unorderedList2 = (1..numberCount).collect{random.nextInt()}
def timeit = {String message, Closure cl->
def startTime = System.currentTimeMillis()
cl()
def deltaTime = System.currentTimeMillis() - startTime
println "$message: \ttime: $deltaTime"
}
timeit("compare using closure") {
def comparator= [ compare: { a,b -> return b <=> a }] as Comparator
unorderedList1.sort(comparator)
}
timeit("compare using method") {
Comparator comparator = new MyComparator()
unorderedList2.sort(comparator)
}
class MyComparator implements Comparator {
int compare(a, b) {return b <=> a}
}
What can I do to improve closure performance?
Wait. JDK7 will add a new instruction, known as invokeDynamic that is expected to radically improve the performance of dynamic languages on the JVM.
Just an update with Groovy 2.0.5 with OpenJDK 1.6 (O6) and JDK 1.7 (J7) on Ubuntu.
I also added two possible implementations:
a method annotated with @CompileStatic:
def numberCount = 10000
def random = new Random()
def unorderedList1 = (1..numberCount).collect{random.nextInt()}
def unorderedList2 = (1..numberCount).collect{random.nextInt()}
def unorderedList3 = (1..numberCount).collect{random.nextInt()}
def unorderedList4 = (1..numberCount).collect{random.nextInt()}
def timeit = {String message, Closure cl->
def startTime = System.currentTimeMillis()
cl()
def deltaTime = System.currentTimeMillis() - startTime
println "$message: \ttime: $deltaTime"
}
timeit("compare using map of closures") {
def comparator= [ compare: { a,b -> return b <=> a }] as Comparator
unorderedList1.sort(comparator)
}
timeit("compare using one closure") {
def comparator= { a, b -> return b <=> a } as Comparator
unorderedList2.sort(comparator)
}
timeit("compare using method") {
Comparator comparator = new MyComparator()
unorderedList3.sort(comparator)
}
timeit("compare using method with @CompileStatic") {
Comparator comparator = new MyComparator2()
unorderedList4.sort(comparator)
}
class MyComparator implements Comparator {
int compare(a, b) {return b <=> a}
}
class MyComparator2 implements Comparator<Integer> {
@groovy.transform.CompileStatic
int compare(Integer a, Integer b) {return b <=> a}
}
Groovy 2.0.5 groovy groovyc O6 O6 J7 closure map 258 499 146 one closure 64 205 48 method 29 37 32 @CompileStatic method 28 26 22
Note that I don't have an install of the Groovy command line compiled with JDK7 (the one I pulled automatically installs its own OpenJDK6), therefore the lack of a corresponding column.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With