benchmark: reproducible random lists

This commit is contained in:
Daniel Langbein 2024-12-12 15:31:00 +00:00
parent e3b90c83cc
commit 025193595c
Signed by: langfingaz
GPG Key ID: 6C47C753F0823002
6 changed files with 81 additions and 45 deletions

View File

@ -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 ? // 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() {
// 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? // A new MainJmh object is created for each @Param variation.
// TODO: This leads to different random lists used for each sort impl. // Then, `data` is `null` again.
// if (data == null) {
// TODO: This is better anyways: Store random list on disk and reuse it for reproducibility
data = dataEnum.get(); 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(); workingCopy = data.getCopy();
} }

View File

@ -4,14 +4,10 @@ public class IntegerArray {
private IntegerArray() { private IntegerArray() {
} }
public static Integer[] random(final int length) { public static Integer[] random(final int length, final long seed) {
return random(length, Integer.MIN_VALUE, Integer.MAX_VALUE);
}
public static Integer[] random(final int length, final int minInt, final int maxInt) {
final Integer[] list = new Integer[length]; final Integer[] list = new Integer[length];
for (int i = 0; i < length; i++) { for (int i = 0; i < length; i++) {
list[i] = RandomInt.integer(minInt, maxInt); list[i] = RandomInt.integer(seed);
} }
return list; return list;
} }

View File

@ -1,5 +1,7 @@
package de.uni_marburg.powersort.benchmark; package de.uni_marburg.powersort.benchmark;
import java.util.Random;
/** /**
* Provides utility methods related to random integers. * Provides utility methods related to random integers.
*/ */
@ -8,15 +10,33 @@ public final class RandomInt {
} }
/** /**
* Returns a random integer.
*
* @return A random integer. * @return A random integer.
*/ */
public static int integer() { public static int integer() {
return integer(Integer.MIN_VALUE, Integer.MAX_VALUE); return integer(Integer.MIN_VALUE, Integer.MAX_VALUE);
} }
public static int integer(final int minInt, final int maxInt) { public static int integer(final int minInt, final int maxInt) {
final double random = Math.random() * (maxInt - minInt) - minInt; final double rand01 = Math.random();
return (int) Math.round(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);
} }
} }

View File

@ -6,8 +6,12 @@ public enum DataEnum {
DESCENDING_INTEGERS; DESCENDING_INTEGERS;
public ObjectSupplier get() { 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) { return switch (this) {
case RANDOM_INTEGERS -> new RandomIntegers(); case RANDOM_INTEGERS -> new RandomIntegers(seed);
case ASCENDING_INTEGERS -> new AscendingIntegers(); case ASCENDING_INTEGERS -> new AscendingIntegers();
case DESCENDING_INTEGERS -> new DescendingIntegers(); case DESCENDING_INTEGERS -> new DescendingIntegers();
}; };

View File

@ -6,8 +6,8 @@ import de.uni_marburg.powersort.benchmark.LongFormatter;
import static de.uni_marburg.powersort.data.DataArraySizes.SIZE_RAND; import static de.uni_marburg.powersort.data.DataArraySizes.SIZE_RAND;
public class RandomIntegers extends IntegerSupplier { public class RandomIntegers extends IntegerSupplier {
public RandomIntegers() { public RandomIntegers(final long seed) {
super(IntegerArray.random(SIZE_RAND)); super(IntegerArray.random(SIZE_RAND, seed));
} }
@Override @Override

View File

@ -3,6 +3,8 @@ package de.uni_marburg.powersort.benchmark;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class RandomIntTest { class RandomIntTest {
protected RandomIntTest() { protected RandomIntTest() {
// This constructor is intentionally empty. Nothing special is needed here. // This constructor is intentionally empty. Nothing special is needed here.
@ -33,4 +35,14 @@ class RandomIntTest {
Assertions.fail("min or max not reached - not a random int generator"); 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);
}
} }