From 648af1c4bee655878e70814107aeac030d240c0d Mon Sep 17 00:00:00 2001 From: Daniel Langbein Date: Tue, 7 Jan 2025 16:43:15 +0000 Subject: [PATCH] benchmark: separate JMH benchmarks --- app/build.gradle.kts | 28 +++++++++++++++-- .../benchmark/{MainJmh.java => JmhBase.java} | 29 ++++++++++-------- .../powersort/benchmark/JmhCompetition.java | 30 +++++++++++++++++++ .../powersort/benchmark/SortImpl.java | 14 --------- .../powersort/data/ObjectSupplier.java | 2 +- 5 files changed, 73 insertions(+), 30 deletions(-) rename app/src/jmh/java/de/uni_marburg/powersort/benchmark/{MainJmh.java => JmhBase.java} (81%) create mode 100644 app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java delete mode 100644 app/src/main/java/de/uni_marburg/powersort/benchmark/SortImpl.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 74f5fe8..e61acfb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,7 +6,9 @@ plugins { // Apply the application plugin to add support for building a CLI application in Java. application + // JMH Gradle Plugin + // https://github.com/melix/jmh-gradle-plugin/blob/master/README.adoc#usage id("me.champeau.jmh") version "0.7.2" } @@ -15,14 +17,17 @@ repositories { mavenCentral() } +val latestJmhVersion = "1.37" + dependencies { // Use JUnit Jupiter for testing. testImplementation(libs.junit.jupiter) testRuntimeOnly("org.junit.platform:junit-platform-launcher") - // This dependency is used by the application. - //implementation(libs.guava) + // Required to use the IntelliJ plugin "JMH Java Microbenchmark Harness". + // 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. @@ -57,4 +62,21 @@ tasks.named("test") { // HTML reports to view with a webbrowser. html.required.set(true) } -} \ No newline at end of file +} + +// 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", + ) +} diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/MainJmh.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java similarity index 81% rename from app/src/jmh/java/de/uni_marburg/powersort/benchmark/MainJmh.java rename to app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java index 3327260..237af37 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/MainJmh.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java @@ -3,6 +3,7 @@ package de.uni_marburg.powersort.benchmark; import de.uni_marburg.powersort.data.DataEnum; import de.uni_marburg.powersort.data.ObjectSupplier; 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.BenchmarkMode; 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.Mode; import org.openjdk.jmh.annotations.OutputTimeUnit; -import org.openjdk.jmh.annotations.Param; import org.openjdk.jmh.annotations.Scope; import org.openjdk.jmh.annotations.Setup; 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(Scope.Benchmark) -public class MainJmh { - @Param() - private DataEnum dataEnum; - @Param() - private SortEnum sortEnum; +public class JmhBase { + DataEnum getDataEnum(){ + return null; + } + SortEnum getSortEnum(){ + return 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 ? @Setup(Level.Invocation) 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. // 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. @@ -59,12 +64,12 @@ public class MainJmh { 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. 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. // Instead, we have to create a copy of it on which the sort algorithm can work. // This way, all iterations sort the same input. @@ -73,6 +78,6 @@ public class MainJmh { @Benchmark public void benchmark() { - sortEnum.getSortImpl().sort(workingCopy); + sortImpl.sort(workingCopy); } } diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java new file mode 100644 index 0000000..2fc07c6 --- /dev/null +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java @@ -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; + } +} diff --git a/app/src/main/java/de/uni_marburg/powersort/benchmark/SortImpl.java b/app/src/main/java/de/uni_marburg/powersort/benchmark/SortImpl.java deleted file mode 100644 index e9552fd..0000000 --- a/app/src/main/java/de/uni_marburg/powersort/benchmark/SortImpl.java +++ /dev/null @@ -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); -} diff --git a/app/src/main/java/de/uni_marburg/powersort/data/ObjectSupplier.java b/app/src/main/java/de/uni_marburg/powersort/data/ObjectSupplier.java index f2814cd..e7e59db 100644 --- a/app/src/main/java/de/uni_marburg/powersort/data/ObjectSupplier.java +++ b/app/src/main/java/de/uni_marburg/powersort/data/ObjectSupplier.java @@ -3,7 +3,7 @@ package de.uni_marburg.powersort.data; import java.util.Arrays; public abstract class ObjectSupplier { - /* package-protected */ final Object[] readOnly; + final Object[] readOnly; ObjectSupplier(Object[] readOnly) { this.readOnly = readOnly;