This is an automated email from the ASF dual-hosted git repository. aherbert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-rng.git
The following commit(s) were added to refs/heads/master by this push: new e37e90e4 RNG-187: Add benchmarks for shuffle without index checks e37e90e4 is described below commit e37e90e463f67d186fa68dd501f1c24a31a08cb1 Author: Alex Herbert <aherb...@apache.org> AuthorDate: Mon Aug 26 22:14:38 2024 +0100 RNG-187: Add benchmarks for shuffle without index checks --- .../jmh/sampling/ArrayShuffleBenchmark.java | 57 ++++++++++++++++++++-- .../jmh/sampling/ArrayShuffleBenchmarkTest.java | 20 ++++++++ 2 files changed, 73 insertions(+), 4 deletions(-) diff --git a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmark.java b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmark.java index 419b44d0..63a84652 100644 --- a/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmark.java +++ b/commons-rng-examples/examples-jmh/src/main/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmark.java @@ -152,7 +152,10 @@ public class ArrayShuffleBenchmark { /** * Method name. */ - @Param({"shuffle", "shuffle2", "shuffle3", "shuffle4"}) + @Param({"shuffle1", + // Effectively the same speed as shuffle1 + //"shuffle1a", + "shuffle2", "shuffle3", "shuffle4"}) private String method; /** Shuffle function. */ @@ -172,8 +175,10 @@ public class ArrayShuffleBenchmark { */ @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)) { @@ -214,6 +219,50 @@ public class ArrayShuffleBenchmark { 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. @@ -283,7 +332,7 @@ public class ArrayShuffleBenchmark { } /** - * 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. * @@ -372,7 +421,7 @@ public class ArrayShuffleBenchmark { } /** - * 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. * diff --git a/commons-rng-examples/examples-jmh/src/test/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmarkTest.java b/commons-rng-examples/examples-jmh/src/test/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmarkTest.java index 4922ee7e..7a105824 100644 --- a/commons-rng-examples/examples-jmh/src/test/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmarkTest.java +++ b/commons-rng-examples/examples-jmh/src/test/java/org/apache/commons/rng/examples/jmh/sampling/ArrayShuffleBenchmarkTest.java @@ -125,4 +125,24 @@ class ArrayShuffleBenchmarkTest { 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); + } + } }