Static Compilation
Prerequisites: Optional typing and type checking
@CompileStatic goes one step further than @TypeChecked: besides
checking types, it generates direct method calls like Java does,
instead of dynamic dispatch through Groovy's runtime. The result is
faster code with Java-like behavior.
import groovy.transform.CompileStatic
@CompileStatic
class Stats {
static double mean(List<Integer> xs) {
(double) xs.sum() / xs.size()
}
static long sumOfSquares(int n) {
long total = 0
for (int i = 1; i <= n; i++) total += (long) i * i
total
}
}
println Stats.mean([2, 4, 9])
println Stats.sumOfSquares(1_000_000)
5.0
333333833333500000
GDK methods (sum, collect, each) still work in static code.
Measuring the difference
import groovy.transform.CompileStatic
long dynamicLoop(int n) {
long total = 0
for (int i = 0; i < n; i++) total += i % 7
total
}
@CompileStatic
long staticLoop(int n) {
long total = 0
for (int i = 0; i < n; i++) total += i % 7
total
}
def time(String label, Closure work) {
work() // warm up the JIT
def start = System.nanoTime()
def result = work()
def ms = (System.nanoTime() - start).intdiv(1_000_000)
println "$label: result $result"
ms
}
def d = time('dynamic') { dynamicLoop(20_000_000) }
def s = time('static') { staticLoop(20_000_000) }
// println "dynamic: $d ms, static: $s ms" // uncomment to see your timings
dynamic: result 59999997
static: result 59999997
Uncomment the last line to see the timings on your machine — typically
the static loop is several times faster. (Timings vary from run to run,
so they're not printed here.) Modern
Groovy's dynamic calls use invokedynamic and are much faster than
they used to be, but for hot numeric loops static compilation still
wins clearly.
What static compilation changes
- Method calls are chosen at compile time from the declared types — like Java's overloading, not Groovy's runtime choice.
- Runtime metaprogramming (adding methods to a metaclass,
methodMissing) is not seen by static code. - Builders and DSLs based on
delegateneed@DelegatesTohints (Level 3) or must stay dynamic.
Mixing dynamic and static
import groovy.transform.CompileStatic
import groovy.transform.CompileDynamic
@CompileStatic
class Report {
String title = 'Q3'
String render() { "Report ${title}: ${extra()}" }
@CompileDynamic
String extra() {
def data = [a: 1]
data.anything?.toString() ?: 'dynamic part'
}
}
println new Report().render()
Report Q3: dynamic part
Put @CompileStatic on classes and opt specific methods out with
@CompileDynamic. Many teams also enable static compilation for a whole
project through a compiler configuration script, so every class is
static by default.