

Testing Groovy/JVM scripting engine performance
source link: https://stormcloak.games/2022/04/05/testing-groovy-jvm-performance
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Testing Groovy/JVM scripting engine performance
5 April 2022 • Jevon Wright
(Warning: very technical gamedev post)
I did some quick testing on the performance of Java and Groovy (my preferred scripting engine), to see where various game logic should occur, if it’s being called potentially tens of thousands of times per second.
Test specifications
Using Java 1.8 on Windows, Groovy 3.0.8, for 300k iterations of a simple a < b
check. I ran the benchmark five times
so that the JVM and Groovy could warm up a bit first, but capturing the warmup/startup time is still useful, too.
Method
First run, ms
Final run, ms
a < b
19
1
get("a") < get("b")
6
3
op(get("a"), get("b"), "<")
13
3
eval("a < b", sharedBindings)
1422
622
eval("a < b", new Bindings(...))
699
688
eval("return get('a') < get('b')", sharedBindings)
837
772
eval("return get('a') < get('b')", new Bindings(...))
782
778
Summary
As soon as you enter the Groovy scripting engine – at least the way that I’m doing it – performance slows down by about ~100x.
Jumping back into the JVM to get data slows execution down by about 10%, so it’s better to put data into the bindings than to jump out of the Groovy scripting context.
Source
Using the following source file:
public class GroovyPerformanceTest {
public static int runs = 5;
public static long iterations = 300000l;
public static void main(String[] arg) throws ScriptException {
new GroovyPerformanceTest().run();
}
public void run() throws ScriptException {
long time = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
// --
for (long j = 0; j < iterations; j++) {
expect(doInlineJava(i, j));
}
System.out.println("doInlineJava -> " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
// --
for (long j = 0; j < iterations; j++) {
expect(doInlineJavaWithSomeSelectSwitches(i, j));
}
System.out.println("doInlineJavaWithSomeSelectSwitches -> " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
// --
for (long j = 0; j < iterations; j++) {
expect(doInlineJavaWithSomeSelectSwitchesAndSelectOperation(i, j));
}
System.out.println("doInlineJavaWithSomeSelectSwitchesAndSelectOperation -> " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
// --
for (long j = 0; j < iterations; j++) {
expect(doGroovyExecutionSharedBindings(i, j));
}
System.out.println("doGroovyExecutionSharedBindings -> " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
// --
for (long j = 0; j < iterations; j++) {
expect(doGroovyExecutionNewBindings(i, j));
}
System.out.println("doGroovyExecutionNewBindings -> " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
// --
for (long j = 0; j < iterations; j++) {
expect(doGroovyExecutionWithSelectionBackIntoJvm(i, j));
}
System.out.println("doGroovyExecutionWithSelectionBackIntoJvm -> " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
// --
for (long j = 0; j < iterations; j++) {
expect(doGroovyExecutionWithSelectionBackIntoJvmNewBindings(i, j));
}
System.out.println("doGroovyExecutionWithSelectionBackIntoJvmNewBindings -> " + (System.currentTimeMillis() - time));
time = System.currentTimeMillis();
}
}
private void expect(boolean b) {
if (!b) {
throw new IllegalStateException();
}
}
public boolean doInlineJava(int i, long j) {
double a = 1.0 + MathUtils.random(1f);
double b = 2.0 + MathUtils.random(1f);
return a < b;
}
public boolean doInlineJavaWithSomeSelectSwitches(int i, long j) {
double a = getSelectValue("a");
double b = getSelectValue("b");
return a < b;
}
public boolean doInlineJavaWithSomeSelectSwitchesAndSelectOperation(int i, long j) {
double a = getSelectValue("a");
double b = getSelectValue("b");
return doOperation(a, b, "<");
}
private double getSelectValue(String s) {
switch (s) {
case "a": return 1.0 + MathUtils.random(1f);
case "b": return 2.0 + MathUtils.random(1f);
case "c": return 3.0 + MathUtils.random(1f);
case "d": return 4.0 + MathUtils.random(1f);
case "e": return 5.0 + MathUtils.random(1f);
case "f": return 6.0 + MathUtils.random(1f);
case "g": return 7.0 + MathUtils.random(1f);
case "h": return 8.0 + MathUtils.random(1f);
case "i": return 9.0 + MathUtils.random(1f);
case "j": return 10.0 + MathUtils.random(1f);
default: throw new IllegalArgumentException(s);
}
}
private boolean doOperation(double a, double b, String op) {
switch (op) {
case "<": return a < b;
case ">": return a > b;
default: throw new IllegalArgumentException(op);
}
}
private SimpleBindings bindings;
private void initBindings(SimpleBindings bindings) {
bindings.put("a", getSelectValue("a"));
bindings.put("b", getSelectValue("b"));
bindings.put("c", getSelectValue("c"));
bindings.put("d", getSelectValue("d"));
bindings.put("e", getSelectValue("e"));
bindings.put("f", getSelectValue("f"));
bindings.put("g", getSelectValue("g"));
bindings.put("h", getSelectValue("h"));
bindings.put("i", getSelectValue("i"));
bindings.put("j", getSelectValue("j"));
}
public boolean doGroovyExecutionSharedBindings(int i, long j) throws ScriptException {
if (bindings == null) {
bindings = new SimpleBindings();
initBindings(bindings);
}
return (boolean) requireNonNull(eval("return a < b", bindings));
}
public boolean doGroovyExecutionNewBindings(int i, long j) throws ScriptException {
SimpleBindings localBindings = new SimpleBindings();
initBindings(localBindings);
return (boolean) requireNonNull(eval("return a < b", localBindings));
}
private SimpleBindings returnBindings;
/** Callback with a string returning an Object. */
@FunctionalInterface
public static interface StringReturningDoubleCallback {
double call(String s);
}
public boolean doGroovyExecutionWithSelectionBackIntoJvm(int i, long j) throws ScriptException {
if (returnBindings == null) {
returnBindings = new SimpleBindings();
returnBindings.put("get", new StringReturningDoubleCallback() {
@Override
public double call(String s) {
return getSelectValue(s);
}
});
}
return (boolean) requireNonNull(eval("return get('a') < get('b')", returnBindings));
}
public boolean doGroovyExecutionWithSelectionBackIntoJvmNewBindings(int i, long j) throws ScriptException {
SimpleBindings localBindings = new SimpleBindings();
localBindings.put("get", new StringReturningDoubleCallback() {
@Override
public double call(String s) {
return getSelectValue(s);
}
});
return (boolean) requireNonNull(eval("return get('a') < get('b')", localBindings));
}
/**
* Scripting engine. Cached between calls so that we don't have to continually
* reinit it (it takes a few seconds to create).
*/
private static GroovyScriptEngineImpl engine = null;
private static @Nullable Object eval(String command, Bindings bindings) throws ScriptException {
if (engine == null) {
System.out.println("Spawning Groovy engine...");
ScriptEngineManager manager = new ScriptEngineManager();
engine = (GroovyScriptEngineImpl) manager.getEngineByName("groovy");
}
engine.setBindings(bindings, ScriptContext.GLOBAL_SCOPE);
return engine.eval(command, bindings);
}
}
Recommend
-
94
groovy-emacs-modes - A groovy major mode, grails minor mode, and a groovy inferior mode.
-
6
Scala Scripting and the 15 Minute Blog Engine Posted 2016-07-30The Scala programming language has traditionally been a to...
-
10
It occurred to me that bloggers might be motivated to improve the quality of their writing if there were some kind of KPI they could chase. No such thing exists for blog posts, especially not those predominantly about software performance and...
-
13
Technical Articles
-
7
Customer GuidanceWelcome to the new and improved Security Update Gui...
-
14
Product Information
-
8
Vulristics Microsoft Patch Tuesday July 2021: Zero-days EoP in Kernel and RCE in Scripting Engine, RCEs in Kernel, DNS Server, Exchange and Hyper-V
-
9
With the recent JDK Mission Control (JMC) 8.1 release, now is a good time to look at the new
-
6
Vikas Jangid April 8, 2021 4 minute read
-
8
JVM Performance Comparison for JDK 17 Authors Ionut Balosin Website: www.ionutbalosin.com Twitter: @ionutbalosin Mastodon: [email protected] Florin Blanaru Twit...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK