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 ?
@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();
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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();
};

View File

@ -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

View File

@ -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);
}
}