Skip to content

Commit

Permalink
RNG-187: Add benchmarks for shuffle without index checks
Browse files Browse the repository at this point in the history
  • Loading branch information
aherbert committed Aug 26, 2024
1 parent 8a76033 commit e37e90e
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ public static class ShuffleMethod {
/**
* Method name.
*/
@Param({"shuffle", "shuffle2", "shuffle3", "shuffle4"})
@Param({"shuffle1",
// Effectively the same speed as shuffle1
//"shuffle1a",
"shuffle2", "shuffle3", "shuffle4"})
private String method;

/** Shuffle function. */
Expand All @@ -172,8 +175,10 @@ public BiConsumer<UniformRandomProvider, int[]> getMethod() {
*/
@Setup
public void setup() {
if ("shuffle".equals(method)) {
if ("shuffle1".equals(method)) {
fun = ArrayShuffleBenchmark::shuffle1;
} else if ("shuffle1a".equals(method)) {
fun = ArrayShuffleBenchmark::shuffle1a;
} else if ("shuffle2".equals(method)) {
fun = ArrayShuffleBenchmark::shuffle2;
} else if ("shuffle3".equals(method)) {
Expand Down Expand Up @@ -214,6 +219,50 @@ static int[] shuffle1(UniformRandomProvider rng, int[] array) {
return array;
}

/**
* Generates an {@code int} value between 0 (inclusive) and the specified value
* (exclusive).
*
* <p>Taken from {@code o.a.c.rng.UniformRandomProviderSupport}. This is used
* to benchmark elimination of the conditional check for a negative index in
* {@link UniformRandomProvider#nextInt(int)}.
*
* @param source Source of randomness.
* @param n Bound on the random number to be returned. Must be strictly positive.
* @return a random {@code int} value between 0 (inclusive) and {@code n} (exclusive).
*/
static int nextInt(UniformRandomProvider source,
int n) {
// Lemire (2019): Fast Random Integer Generation in an Interval
// https://arxiv.org/abs/1805.10941
long m = (source.nextInt() & 0xffffffffL) * n;
long l = m & 0xffffffffL;
if (l < n) {
// 2^32 % n
final long t = POW_32 % n;
while (l < t) {
m = (source.nextInt() & 0xffffffffL) * n;
l = m & 0xffffffffL;
}
}
return (int) (m >>> 32);
}

/**
* Shuffles the entries of the given array.
* Uses a Fisher-Yates shuffle.
*
* @param rng Source of randomness.
* @param array Array whose entries will be shuffled (in-place).
* @return a reference to the given array
*/
static int[] shuffle1a(UniformRandomProvider rng, int[] array) {
for (int i = array.length; i > 1; i--) {
swap(array, i - 1, nextInt(rng, i));
}
return array;
}

/**
* Return two random values in {@code [0, range1)} and {@code [0, range2)}. The
* product bound is used for the reject algorithm. See Brackett-Rozinsky and Lemire.
Expand Down Expand Up @@ -283,7 +332,7 @@ static int[] shuffle2(UniformRandomProvider rng, int[] array) {
}

/**
* Return two random values in {@code [0, range1)}, {@code [0, range2)}
* Return three random values in {@code [0, range1)}, {@code [0, range2)}
* and {@code [0, range3)}. The
* product bound is used for the reject algorithm. See Brackett-Rozinsky and Lemire.
*
Expand Down Expand Up @@ -372,7 +421,7 @@ static int[] shuffle3(UniformRandomProvider rng, int[] array) {
}

/**
* Return two random values in {@code [0, range1)}, {@code [0, range2)},
* Return four random values in {@code [0, range1)}, {@code [0, range2)},
* {@code [0, range3)} and {@code [0, range4)}. The
* product bound is used for the reject algorithm. See Brackett-Rozinsky and Lemire.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,24 @@ private static void assertShuffle(int length, BiConsumer<UniformRandomProvider,
final double p = new ChiSquareTest().chiSquareTest(observed);
Assertions.assertFalse(p < 1e-3, () -> "p-value too small: " + p);
}

@ParameterizedTest
@CsvSource({
"257",
"8073",
})
void testShuffle1a(int length) {
final int[] a = PermutationSampler.natural(length);
final int[] b = a.clone();
final RandomSource source = RandomSource.XO_RO_SHI_RO_128_PP;
final byte[] seed = source.createSeed();
final UniformRandomProvider rng1 = source.create(seed);
final UniformRandomProvider rng2 = source.create(seed);
final int samples = 10;
for (int j = 0; j < samples; j++) {
ArrayShuffleBenchmark.shuffle1(rng1, a);
ArrayShuffleBenchmark.shuffle1(rng2, b);
Assertions.assertArrayEquals(a, b);
}
}
}

0 comments on commit e37e90e

Please sign in to comment.