benchmark: separate JMH benchmarks

This commit is contained in:
Daniel Langbein 2025-01-07 16:43:15 +00:00
parent 1a356490e4
commit 648af1c4be
Signed by: langfingaz
GPG Key ID: 6C47C753F0823002
5 changed files with 73 additions and 30 deletions

View File

@ -6,7 +6,9 @@
plugins { plugins {
// Apply the application plugin to add support for building a CLI application in Java. // Apply the application plugin to add support for building a CLI application in Java.
application application
// JMH Gradle Plugin // JMH Gradle Plugin
// https://github.com/melix/jmh-gradle-plugin/blob/master/README.adoc#usage
id("me.champeau.jmh") version "0.7.2" id("me.champeau.jmh") version "0.7.2"
} }
@ -15,14 +17,17 @@ repositories {
mavenCentral() mavenCentral()
} }
val latestJmhVersion = "1.37"
dependencies { dependencies {
// Use JUnit Jupiter for testing. // Use JUnit Jupiter for testing.
testImplementation(libs.junit.jupiter) testImplementation(libs.junit.jupiter)
testRuntimeOnly("org.junit.platform:junit-platform-launcher") testRuntimeOnly("org.junit.platform:junit-platform-launcher")
// This dependency is used by the application. // Required to use the IntelliJ plugin "JMH Java Microbenchmark Harness".
//implementation(libs.guava) // https://stackoverflow.com/a/71286156/6334421
jmhAnnotationProcessor("org.openjdk.jmh:jmh-generator-annprocess:$latestJmhVersion")
} }
// Apply a specific Java toolchain to ease working on different environments. // Apply a specific Java toolchain to ease working on different environments.
@ -57,4 +62,21 @@ tasks.named<Test>("test") {
// HTML reports to view with a webbrowser. // HTML reports to view with a webbrowser.
html.required.set(true) html.required.set(true)
} }
} }
// https://github.com/melix/jmh-gradle-plugin/blob/master/README.adoc#configuration-options
jmh {
// Use latest JMH version (1.37, https://github.com/openjdk/jmh/tags)
// instead of default (1.36, https://github.com/melix/jmh-gradle-plugin/blob/dfdbb0cd3a363e2dbbcaab93bb1be32178f4cae4/src/main/java/me/champeau/jmh/DefaultsConfigurer.java#L24)
jmhVersion = latestJmhVersion // Specifies JMH version
// Force GC (garbage collection) between iterations.
//
// Forcing the GC may help to lower the noise in GC-heavy benchmarks, at the expense of jeopardizing GC ergonomics decisions. Use with care.
// https://github.com/openjdk/jmh/blob/1be779e0b4af174b67abf8080d1b76dda570d88d/jmh-core/src/main/java/org/openjdk/jmh/runner/options/CommandLineOptions.java#L148-L152
forceGC = true
excludes = listOf(
"de.uni_marburg.powersort.benchmark.JmhBase.benchmark",
)
}

View File

@ -3,6 +3,7 @@ package de.uni_marburg.powersort.benchmark;
import de.uni_marburg.powersort.data.DataEnum; import de.uni_marburg.powersort.data.DataEnum;
import de.uni_marburg.powersort.data.ObjectSupplier; import de.uni_marburg.powersort.data.ObjectSupplier;
import de.uni_marburg.powersort.sort.SortEnum; import de.uni_marburg.powersort.sort.SortEnum;
import de.uni_marburg.powersort.sort.SortImpl;
import org.openjdk.jmh.annotations.Benchmark; import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode; import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork; import org.openjdk.jmh.annotations.Fork;
@ -10,7 +11,6 @@ import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Measurement; import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit; import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State; import org.openjdk.jmh.annotations.State;
@ -36,19 +36,24 @@ import java.util.concurrent.TimeUnit;
* State objects naturally encapsulate the state on which benchmark is working on. * State objects naturally encapsulate the state on which benchmark is working on.
*/ */
@State(Scope.Benchmark) @State(Scope.Benchmark)
public class MainJmh { public class JmhBase {
@Param() DataEnum getDataEnum(){
private DataEnum dataEnum; return null;
@Param() }
private SortEnum sortEnum; SortEnum getSortEnum(){
return null;
}
private ObjectSupplier data = null; private ObjectSupplier data = null;
/* package-protected */ Object[] workingCopy; Object[] workingCopy;
SortImpl sortImpl;
// TODO: This is inaccurate. How to create and use separate arrays for each warmup x iteration x sortAlgorithm ? // TODO: This is inaccurate. How to create and use separate arrays for each warmup x iteration x sortAlgorithm ?
@Setup(Level.Invocation) @Setup(Level.Invocation)
public void setup() { public void setup() {
if(Filter.isFiltered(dataEnum, sortEnum)) { sortImpl = getSortEnum().getSortImpl();
if (Filter.isFiltered(getDataEnum(), getSortEnum())) {
// This combination of DataEnum and SortEnum should be skipped. // This combination of DataEnum and SortEnum should be skipped.
// We can't tell JMH to not run the benchmark at all. // We can't tell JMH to not run the benchmark at all.
// Instead, we let it sort an invalid list which results in an exception. // Instead, we let it sort an invalid list which results in an exception.
@ -59,12 +64,12 @@ public class MainJmh {
return; return;
} }
// A new MainJmh object is created for each @Param variation. // A new JmhRunner object is created for each @Param variation.
// Then, `data` is `null` again. // Then, `data` is `null` again.
if (data == null) { if (data == null) {
data = dataEnum.getObjectSupplier(); data = getDataEnum().getObjectSupplier();
} }
// For all warmup and measurement iterations of one @Param variation, the MainJmh object is reused. // For all warmup and measurement iterations of one @Param variation, the JmhRunner object is reused.
// Thus, we can't just sort `data` directly. // Thus, we can't just sort `data` directly.
// Instead, we have to create a copy of it on which the sort algorithm can work. // Instead, we have to create a copy of it on which the sort algorithm can work.
// This way, all iterations sort the same input. // This way, all iterations sort the same input.
@ -73,6 +78,6 @@ public class MainJmh {
@Benchmark @Benchmark
public void benchmark() { public void benchmark() {
sortEnum.getSortImpl().sort(workingCopy); sortImpl.sort(workingCopy);
} }
} }

View File

@ -0,0 +1,30 @@
package de.uni_marburg.powersort.benchmark;
import de.uni_marburg.powersort.data.DataEnum;
import de.uni_marburg.powersort.sort.SortEnum;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
/*
* Benchmark state parameters
*
* Quote from JMH:
* State objects naturally encapsulate the state on which benchmark is working on.
*/
@State(Scope.Benchmark)
public class JmhCompetition extends JmhBase {
@Param()
DataEnum dataEnum;
@Param()
SortEnum sortEnum;
@Override
DataEnum getDataEnum(){
return dataEnum;
}
@Override
SortEnum getSortEnum(){
return sortEnum;
}
}

View File

@ -1,14 +0,0 @@
package de.uni_marburg.powersort.benchmark;
public abstract class SortImpl {
final String title;
public SortImpl(String title) {
this.title = title;
}
/**
* Apply the sort algorithm.
*/
public abstract void sort(Object[] a);
}

View File

@ -3,7 +3,7 @@ package de.uni_marburg.powersort.data;
import java.util.Arrays; import java.util.Arrays;
public abstract class ObjectSupplier { public abstract class ObjectSupplier {
/* package-protected */ final Object[] readOnly; final Object[] readOnly;
ObjectSupplier(Object[] readOnly) { ObjectSupplier(Object[] readOnly) {
this.readOnly = readOnly; this.readOnly = readOnly;