From 025193595c2b85a5e6dac69b256c56a81c4bb6fe Mon Sep 17 00:00:00 2001 From: Daniel Langbein Date: Thu, 12 Dec 2024 15:31:00 +0000 Subject: [PATCH] benchmark: reproducible random lists --- .../powersort/benchmark/MainJmh.java | 14 ++-- .../powersort/benchmark/IntegerArray.java | 8 +-- .../powersort/benchmark/RandomInt.java | 28 ++++++-- .../uni_marburg/powersort/data/DataEnum.java | 6 +- .../powersort/data/RandomIntegers.java | 4 +- .../powersort/benchmark/RandomIntTest.java | 66 +++++++++++-------- 6 files changed, 81 insertions(+), 45 deletions(-) diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/MainJmh.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/MainJmh.java index 1b55391..d0151f6 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/MainJmh.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/MainJmh.java @@ -46,11 +46,15 @@ public class MainJmh { // TODO: This is inaccurate. How to create and use separate arrays for each warmup x iteration x sortAlgorithm ? @Setup(Level.Invocation) public void setup() { - // TODO: We tried to store the old `dataEnum` in a separate variable and only call `dataEnum.get()` if `dataEnum` changed. But the separate variable was always reverted to `null`. Why? Maybe as the benchmark method does not use it? - // TODO: This leads to different random lists used for each sort impl. - // - // TODO: This is better anyways: Store random list on disk and reuse it for reproducibility - data = dataEnum.get(); + // A new MainJmh object is created for each @Param variation. + // Then, `data` is `null` again. + if (data == null) { + data = dataEnum.get(); + } + // For all warmup and measurement iterations of one @Param variation, the MainJmh 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. workingCopy = data.getCopy(); } diff --git a/app/src/main/java/de/uni_marburg/powersort/benchmark/IntegerArray.java b/app/src/main/java/de/uni_marburg/powersort/benchmark/IntegerArray.java index 13a449d..0e54ab0 100644 --- a/app/src/main/java/de/uni_marburg/powersort/benchmark/IntegerArray.java +++ b/app/src/main/java/de/uni_marburg/powersort/benchmark/IntegerArray.java @@ -4,14 +4,10 @@ public class IntegerArray { private IntegerArray() { } - public static Integer[] random(final int length) { - return random(length, Integer.MIN_VALUE, Integer.MAX_VALUE); - } - - public static Integer[] random(final int length, final int minInt, final int maxInt) { + public static Integer[] random(final int length, final long seed) { final Integer[] list = new Integer[length]; for (int i = 0; i < length; i++) { - list[i] = RandomInt.integer(minInt, maxInt); + list[i] = RandomInt.integer(seed); } return list; } diff --git a/app/src/main/java/de/uni_marburg/powersort/benchmark/RandomInt.java b/app/src/main/java/de/uni_marburg/powersort/benchmark/RandomInt.java index 3575cdc..d2330c0 100644 --- a/app/src/main/java/de/uni_marburg/powersort/benchmark/RandomInt.java +++ b/app/src/main/java/de/uni_marburg/powersort/benchmark/RandomInt.java @@ -1,5 +1,7 @@ package de.uni_marburg.powersort.benchmark; +import java.util.Random; + /** * Provides utility methods related to random integers. */ @@ -8,15 +10,33 @@ public final class RandomInt { } /** - * Returns a random integer. - * * @return A random integer. */ public static int integer() { return integer(Integer.MIN_VALUE, Integer.MAX_VALUE); } public static int integer(final int minInt, final int maxInt) { - final double random = Math.random() * (maxInt - minInt) - minInt; - return (int) Math.round(random); + final double rand01 = Math.random(); + return helper(minInt, maxInt, rand01); + } + + /** + * The returned random integer to return is determined by the given seed. + * + * @return A random integer. + */ + public static int integer(final long seed) { + Random random = new Random(seed); + final double rand01 = random.nextDouble(); + return helper(Integer.MIN_VALUE, Integer.MAX_VALUE, rand01); + + } + + /** + * @param rand01 Random value in range [0,1) + */ + private static int helper(final int minInt, final int maxInt, final double rand01) { + final double randMinMax = rand01 * (maxInt - minInt) - minInt; + return (int) Math.round(randMinMax); } } diff --git a/app/src/main/java/de/uni_marburg/powersort/data/DataEnum.java b/app/src/main/java/de/uni_marburg/powersort/data/DataEnum.java index 6b8bd30..6d2ee1e 100644 --- a/app/src/main/java/de/uni_marburg/powersort/data/DataEnum.java +++ b/app/src/main/java/de/uni_marburg/powersort/data/DataEnum.java @@ -6,8 +6,12 @@ public enum DataEnum { DESCENDING_INTEGERS; public ObjectSupplier get() { + // We use a seed to get the same random list every time -> Repeatable benchmarks on same input data! + // final long seed = 3651660232967549736L; // System.nanoTime() ++ Math.random() + final long seed = 140506881906827520L; // (long) 'P' * (long) 'O' *(long) 'W' * (long) 'E' * (long) 'R' * (long) 'S' * (long) 'O' * (long) 'R' * (long) 'T'; + return switch (this) { - case RANDOM_INTEGERS -> new RandomIntegers(); + case RANDOM_INTEGERS -> new RandomIntegers(seed); case ASCENDING_INTEGERS -> new AscendingIntegers(); case DESCENDING_INTEGERS -> new DescendingIntegers(); }; diff --git a/app/src/main/java/de/uni_marburg/powersort/data/RandomIntegers.java b/app/src/main/java/de/uni_marburg/powersort/data/RandomIntegers.java index 8cb36a9..40c46f6 100644 --- a/app/src/main/java/de/uni_marburg/powersort/data/RandomIntegers.java +++ b/app/src/main/java/de/uni_marburg/powersort/data/RandomIntegers.java @@ -6,8 +6,8 @@ import de.uni_marburg.powersort.benchmark.LongFormatter; import static de.uni_marburg.powersort.data.DataArraySizes.SIZE_RAND; public class RandomIntegers extends IntegerSupplier { - public RandomIntegers() { - super(IntegerArray.random(SIZE_RAND)); + public RandomIntegers(final long seed) { + super(IntegerArray.random(SIZE_RAND, seed)); } @Override diff --git a/app/src/test/java/de/uni_marburg/powersort/benchmark/RandomIntTest.java b/app/src/test/java/de/uni_marburg/powersort/benchmark/RandomIntTest.java index 733b44d..ab63a86 100644 --- a/app/src/test/java/de/uni_marburg/powersort/benchmark/RandomIntTest.java +++ b/app/src/test/java/de/uni_marburg/powersort/benchmark/RandomIntTest.java @@ -3,34 +3,46 @@ package de.uni_marburg.powersort.benchmark; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + class RandomIntTest { - protected RandomIntTest(){ - // This constructor is intentionally empty. Nothing special is needed here. - } - - @Test - void testRandomInt() { - final double accuracy = 0.99; - final int min = (int) Math.round(Integer.MIN_VALUE * accuracy); - final int max = (int) Math.round(Integer.MAX_VALUE * accuracy); - - boolean minPassed = false; - boolean maxPassed = false; - for (int i = 0; i < 1000; i++) { - final int random = RandomInt.integer(); - System.out.println(random); //NOPMD - suppressed SystemPrintln - Testing - - if (random <= min) { - minPassed = true; - } - if (random >= max) { - maxPassed = true; - } - if (minPassed && maxPassed) { - return; - } + protected RandomIntTest() { + // This constructor is intentionally empty. Nothing special is needed here. } - Assertions.fail("min or max not reached - not a random int generator"); - } + @Test + void testRandomInt() { + final double accuracy = 0.99; + final int min = (int) Math.round(Integer.MIN_VALUE * accuracy); + final int max = (int) Math.round(Integer.MAX_VALUE * accuracy); + + boolean minPassed = false; + boolean maxPassed = false; + for (int i = 0; i < 1000; i++) { + final int random = RandomInt.integer(); + System.out.println(random); //NOPMD - suppressed SystemPrintln - Testing + + if (random <= min) { + minPassed = true; + } + if (random >= max) { + maxPassed = true; + } + if (minPassed && maxPassed) { + return; + } + } + + Assertions.fail("min or max not reached - not a random int generator"); + } + + @Test + void testReproducibility() { + long seed = 1337; + + int expected = 2147483647; + long actual = RandomInt.integer(seed); + + assertEquals(expected, actual); + } }