Skip to content

Commit b0fce5b

Browse files
committed
Add JMH benchmark harness
1 parent ed0b493 commit b0fce5b

7 files changed

Lines changed: 440 additions & 0 deletions

File tree

gradle/libs.versions.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
checkstyle = "13.3.0"
66
jacoco = "0.8.12"
7+
jmh = "1.37"
78
lwjgl3 = "3.4.1"
89
angle = "2026-05-09"
910
libjglios = "0.4"
@@ -28,6 +29,8 @@ jbullet = "com.github.stephengold:jbullet:1.0.3"
2829
jinput = "net.java.jinput:jinput:2.0.9"
2930
jna = "net.java.dev.jna:jna:5.18.1"
3031
jnaerator-runtime = "com.nativelibs4java:jnaerator-runtime:0.12"
32+
jmh-core = { module = "org.openjdk.jmh:jmh-core", version.ref = "jmh" }
33+
jmh-generator-annprocess = { module = "org.openjdk.jmh:jmh-generator-annprocess", version.ref = "jmh" }
3134
junit-bom = "org.junit:junit-bom:5.13.4"
3235
junit4 = "junit:junit:4.13.2"
3336
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter" }

jme3-benchmark/build.gradle

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
dependencies {
2+
implementation project(':jme3-core')
3+
implementation libs.jmh.core
4+
annotationProcessor libs.jmh.generator.annprocess
5+
}
6+
7+
tasks.register('jmh', JavaExec) {
8+
dependsOn tasks.named('classes')
9+
description = 'Run jME JMH benchmarks. Pass JMH options with -PjmhArgs="...".'
10+
group = 'verification'
11+
classpath = sourceSets.main.runtimeClasspath
12+
mainClass = 'org.openjdk.jmh.Main'
13+
14+
def rawArgs = providers.gradleProperty('jmhArgs').orElse('').map { it.trim() }
15+
doFirst {
16+
if (rawArgs.get()) {
17+
args rawArgs.get().split(/\s+/)
18+
}
19+
}
20+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright (c) 2009-2026 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package com.jme3.bounding;
33+
34+
import com.jme3.math.Vector3f;
35+
import java.util.concurrent.TimeUnit;
36+
import org.openjdk.jmh.annotations.Benchmark;
37+
import org.openjdk.jmh.annotations.BenchmarkMode;
38+
import org.openjdk.jmh.annotations.Fork;
39+
import org.openjdk.jmh.annotations.Level;
40+
import org.openjdk.jmh.annotations.Measurement;
41+
import org.openjdk.jmh.annotations.Mode;
42+
import org.openjdk.jmh.annotations.OutputTimeUnit;
43+
import org.openjdk.jmh.annotations.Param;
44+
import org.openjdk.jmh.annotations.Scope;
45+
import org.openjdk.jmh.annotations.Setup;
46+
import org.openjdk.jmh.annotations.State;
47+
import org.openjdk.jmh.annotations.Warmup;
48+
import org.openjdk.jmh.infra.Blackhole;
49+
50+
@BenchmarkMode(Mode.AverageTime)
51+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
52+
@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
53+
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
54+
@Fork(2)
55+
@State(Scope.Thread)
56+
public class BoundingVolumeBenchmark {
57+
58+
@Param({"inside", "outside"})
59+
public String pointLocation;
60+
61+
private BoundingBox box;
62+
private BoundingSphere sphere;
63+
private Vector3f point;
64+
65+
@Setup(Level.Trial)
66+
public void setupTrial() {
67+
box = new BoundingBox(new Vector3f(3f, -2f, 7f), 5f, 9f, 13f);
68+
sphere = new BoundingSphere(11f, new Vector3f(3f, -2f, 7f));
69+
if ("inside".equals(pointLocation)) {
70+
point = new Vector3f(4f, 0f, 9f);
71+
} else {
72+
point = new Vector3f(39f, -31f, 45f);
73+
}
74+
}
75+
76+
@Benchmark
77+
public void boxDistanceToEdge(Blackhole blackhole) {
78+
blackhole.consume(box.distanceToEdge(point));
79+
}
80+
81+
@Benchmark
82+
public void boxIntersectsPoint(Blackhole blackhole) {
83+
blackhole.consume(box.intersects(point));
84+
}
85+
86+
@Benchmark
87+
public void sphereDistanceToEdge(Blackhole blackhole) {
88+
blackhole.consume(sphere.distanceToEdge(point));
89+
}
90+
91+
@Benchmark
92+
public void sphereIntersectsPoint(Blackhole blackhole) {
93+
blackhole.consume(sphere.intersects(point));
94+
}
95+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/*
2+
* Copyright (c) 2009-2026 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package com.jme3.light;
33+
34+
import com.jme3.math.Vector3f;
35+
import com.jme3.scene.Geometry;
36+
import com.jme3.scene.Mesh;
37+
import java.util.concurrent.TimeUnit;
38+
import org.openjdk.jmh.annotations.Benchmark;
39+
import org.openjdk.jmh.annotations.BenchmarkMode;
40+
import org.openjdk.jmh.annotations.Fork;
41+
import org.openjdk.jmh.annotations.Level;
42+
import org.openjdk.jmh.annotations.Measurement;
43+
import org.openjdk.jmh.annotations.Mode;
44+
import org.openjdk.jmh.annotations.OutputTimeUnit;
45+
import org.openjdk.jmh.annotations.Param;
46+
import org.openjdk.jmh.annotations.Scope;
47+
import org.openjdk.jmh.annotations.Setup;
48+
import org.openjdk.jmh.annotations.State;
49+
import org.openjdk.jmh.annotations.Warmup;
50+
import org.openjdk.jmh.infra.Blackhole;
51+
52+
@BenchmarkMode(Mode.AverageTime)
53+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
54+
@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
55+
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
56+
@Fork(2)
57+
@State(Scope.Thread)
58+
public class LightListMutationBenchmark {
59+
60+
@Param({"8", "64", "256"})
61+
public int lightCount;
62+
63+
private Geometry owner;
64+
private Light[] lights;
65+
private LightList list;
66+
private LightList local;
67+
private LightList parent;
68+
69+
@Setup(Level.Trial)
70+
public void setupTrial() {
71+
owner = new Geometry("owner", new Mesh());
72+
lights = new Light[lightCount * 2];
73+
for (int i = 0; i < lights.length; i++) {
74+
lights[i] = new PointLight(new Vector3f(i, i * 0.5f, -i));
75+
}
76+
list = new LightList(owner);
77+
local = new LightList(owner);
78+
parent = new LightList(owner);
79+
for (int i = 0; i < lightCount; i++) {
80+
local.add(lights[i]);
81+
parent.add(lights[lightCount + i]);
82+
}
83+
}
84+
85+
@Setup(Level.Invocation)
86+
public void setupInvocation() {
87+
list.clear();
88+
for (int i = 0; i < lightCount; i++) {
89+
list.add(lights[i]);
90+
}
91+
}
92+
93+
@Benchmark
94+
public void removeFromFront(Blackhole blackhole) {
95+
list.remove(0);
96+
blackhole.consume(list.size());
97+
}
98+
99+
@Benchmark
100+
public void removeFromMiddle(Blackhole blackhole) {
101+
list.remove(lightCount >>> 1);
102+
blackhole.consume(list.size());
103+
}
104+
105+
@Benchmark
106+
public void removeFromEnd(Blackhole blackhole) {
107+
list.remove(lightCount - 1);
108+
blackhole.consume(list.size());
109+
}
110+
111+
@Benchmark
112+
public void updateFromLocalAndParent(Blackhole blackhole) {
113+
list.update(local, parent);
114+
blackhole.consume(list.size());
115+
}
116+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright (c) 2009-2026 jMonkeyEngine
3+
* All rights reserved.
4+
*
5+
* Redistribution and use in source and binary forms, with or without
6+
* modification, are permitted provided that the following conditions are
7+
* met:
8+
*
9+
* * Redistributions of source code must retain the above copyright
10+
* notice, this list of conditions and the following disclaimer.
11+
*
12+
* * Redistributions in binary form must reproduce the above copyright
13+
* notice, this list of conditions and the following disclaimer in the
14+
* documentation and/or other materials provided with the distribution.
15+
*
16+
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
17+
* may be used to endorse or promote products derived from this software
18+
* without specific prior written permission.
19+
*
20+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21+
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22+
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23+
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
24+
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25+
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26+
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27+
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
28+
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
29+
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30+
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
*/
32+
package com.jme3.light;
33+
34+
import com.jme3.math.Vector3f;
35+
import com.jme3.scene.Geometry;
36+
import com.jme3.scene.Mesh;
37+
import java.util.Random;
38+
import java.util.concurrent.TimeUnit;
39+
import org.openjdk.jmh.annotations.Benchmark;
40+
import org.openjdk.jmh.annotations.BenchmarkMode;
41+
import org.openjdk.jmh.annotations.Fork;
42+
import org.openjdk.jmh.annotations.Level;
43+
import org.openjdk.jmh.annotations.Measurement;
44+
import org.openjdk.jmh.annotations.Mode;
45+
import org.openjdk.jmh.annotations.OutputTimeUnit;
46+
import org.openjdk.jmh.annotations.Param;
47+
import org.openjdk.jmh.annotations.Scope;
48+
import org.openjdk.jmh.annotations.Setup;
49+
import org.openjdk.jmh.annotations.State;
50+
import org.openjdk.jmh.annotations.Warmup;
51+
import org.openjdk.jmh.infra.Blackhole;
52+
53+
@BenchmarkMode(Mode.AverageTime)
54+
@OutputTimeUnit(TimeUnit.MICROSECONDS)
55+
@Warmup(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
56+
@Measurement(iterations = 5, time = 200, timeUnit = TimeUnit.MILLISECONDS)
57+
@Fork(2)
58+
@State(Scope.Thread)
59+
public class LightListSortBenchmark {
60+
61+
@Param({"8", "64", "256"})
62+
public int lightCount;
63+
64+
@Param({"1", "8", "1024"})
65+
public int retainedCapacityMultiplier;
66+
67+
private Geometry owner;
68+
private Light[] lights;
69+
private LightList list;
70+
private int invocation;
71+
72+
@Setup(Level.Trial)
73+
public void setupTrial() {
74+
owner = new Geometry("owner", new Mesh());
75+
owner.setLocalTranslation(3f, -7f, 11f);
76+
owner.updateGeometricState();
77+
78+
lights = new Light[lightCount];
79+
Random random = new Random(0x51A7E5L + lightCount);
80+
for (int i = 0; i < lightCount; i++) {
81+
switch (i & 3) {
82+
case 0:
83+
lights[i] = new AmbientLight();
84+
break;
85+
case 1:
86+
lights[i] = new DirectionalLight(new Vector3f(1f, -1f, 0.25f).normalizeLocal());
87+
break;
88+
default:
89+
lights[i] = new PointLight(new Vector3f(
90+
random.nextFloat() * 200f - 100f,
91+
random.nextFloat() * 200f - 100f,
92+
random.nextFloat() * 200f - 100f));
93+
break;
94+
}
95+
}
96+
97+
list = new LightList(owner);
98+
int retainedCapacity = Math.max(lightCount, lightCount * retainedCapacityMultiplier);
99+
for (int i = 0; i < retainedCapacity; i++) {
100+
list.add(lights[i % lightCount]);
101+
}
102+
list.clear();
103+
}
104+
105+
@Setup(Level.Invocation)
106+
public void setupInvocation() {
107+
list.clear();
108+
int offset = (invocation++ & Integer.MAX_VALUE) % lightCount;
109+
for (int i = 0; i < lightCount; i++) {
110+
list.add(lights[(i + offset) % lightCount]);
111+
}
112+
}
113+
114+
@Benchmark
115+
public void sortTransformChanged(Blackhole blackhole) {
116+
list.sort(true);
117+
blackhole.consume(list.get(lightCount - 1));
118+
}
119+
}

0 commit comments

Comments
 (0)