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

commit 6fb9ab0f15210535edfb8e5a9c773169ccb8a943
Author: Alex Herbert <aherb...@apache.org>
AuthorDate: Mon Sep 2 12:32:34 2024 +0100

    RNG-187: Add benchmarks for shuffle2 variants
    
    One variant has no memory allocation when generating indices. The other
    accepts arguments [from, to) for the shuffle sub-range.
---
 .../jmh/sampling/ArrayShuffleBenchmark.java        | 117 ++++++++++++++++++++-
 .../jmh/sampling/ArrayShuffleBenchmarkTest.java    |  47 +++++++++
 2 files changed, 159 insertions(+), 5 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 63a84652..163ce6b1 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
@@ -155,7 +155,10 @@ public class ArrayShuffleBenchmark {
         @Param({"shuffle1",
             // Effectively the same speed as shuffle1
             //"shuffle1a",
-            "shuffle2", "shuffle3", "shuffle4"})
+            "shuffle2", "shuffle3", "shuffle4",
+            // Effectively the same speed as shuffle2; the range method is 
marginally slower
+            //"shuffle2a", "shuffle2range",
+            })
         private String method;
 
         /** Shuffle function. */
@@ -181,6 +184,10 @@ public class ArrayShuffleBenchmark {
                 fun = ArrayShuffleBenchmark::shuffle1a;
             } else if ("shuffle2".equals(method)) {
                 fun = ArrayShuffleBenchmark::shuffle2;
+            } else if ("shuffle2a".equals(method)) {
+                fun = ArrayShuffleBenchmark::shuffle2a;
+            } else if ("shuffle2range".equals(method)) {
+                fun = (rng, a) -> ArrayShuffleBenchmark.shuffle2(rng, a, 0, 
a.length);
             } else if ("shuffle3".equals(method)) {
                 fun = ArrayShuffleBenchmark::shuffle3;
             } else if ("shuffle4".equals(method)) {
@@ -239,7 +246,7 @@ public class ArrayShuffleBenchmark {
         long l = m & 0xffffffffL;
         if (l < n) {
             // 2^32 % n
-            final long t = POW_32 % n;
+            final final long t = POW_32 % n;
             while (l < t) {
                 m = (source.nextInt() & 0xffffffffL) * n;
                 l = m & 0xffffffffL;
@@ -290,7 +297,7 @@ public class ArrayShuffleBenchmark {
             productBound[0] = bound;
             if (l < bound) {
                 // 2^32 % bound
-                long t = POW_32 % bound;
+                final long t = POW_32 % bound;
                 while (l < t) {
                     m = (rng.nextInt() & MASK_32) * range1;
                     r1 = m;
@@ -331,6 +338,106 @@ public class ArrayShuffleBenchmark {
         return array;
     }
 
+    /**
+     * Shuffles the entries of the given array.
+     *
+     * @param rng Source of randomness.
+     * @param array Array whose entries will be shuffled (in-place).
+     * @param from Lower-bound (inclusive) of the sub-range.
+     * @param to Upper-bound (exclusive) of the sub-range.
+     * @return a reference to the given array
+     */
+    static int[] shuffle2(UniformRandomProvider rng, int[] array, int from, 
int to) {
+        int i = to - from;
+        // The threshold provided in the Brackett-Rozinsky and Lemire paper
+        // is the power of 2 below 20724. Note that the product 2^15*2^15
+        // is representable using signed integers.
+        for (; i > POW_15; i--) {
+            swap(array, from + i - 1, from + rng.nextInt(i));
+        }
+        // Batches of 2 for sizes up to 2^15 elements
+        final int[] productBound = {i * (i - 1)};
+        for (; i > 1; i -= 2) {
+            final int[] indices = randomBounded2(i, i - 1, productBound, rng);
+            final int index1 = indices[0];
+            final int index2 = indices[1];
+            swap(array, from + i - 1, from + index1);
+            swap(array, from + i - 2, from + index2);
+        }
+        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.
+     *
+     * <p>The product bound can be any positive integer {@code >= 
range1*range2}.
+     * It may be updated to become {@code range1*range2}.
+     *
+     * @param range1 Range 1.
+     * @param range2 Range 2.
+     * @param bound Product bound.
+     * @param rng Source of randomness.
+     * @param indices Output indices.
+     * @return updated bound
+     */
+    static int randomBounded2a(int range1, int range2, int bound, 
UniformRandomProvider rng,
+            int[] indices) {
+        long m = (rng.nextInt() & MASK_32) * range1;
+        // result1 and result2 are the top 32-bits of the long
+        long r1 = m;
+        // Leftover bits * range2
+        m = (m & MASK_32) * range2;
+        long r2 = m;
+        // Leftover bits must be unsigned
+        long l = m & MASK_32;
+        if (l < bound) {
+            bound = range1 * range2;
+            if (l < bound) {
+                // 2^32 % bound
+                final long t = POW_32 % bound;
+                while (l < t) {
+                    m = (rng.nextInt() & MASK_32) * range1;
+                    r1 = m;
+                    m = (m & MASK_32) * range2;
+                    r2 = m;
+                    l = m & MASK_32;
+                }
+            }
+        }
+        // Convert to [0, range1), [0, range2)
+        indices[0] = (int) (r1 >> 32);
+        indices[1] = (int) (r2 >> 32);
+        return bound;
+    }
+
+    /**
+     * Shuffles the entries of the given array. Uses a variant of the random 
bounded
+     * integer generation.
+     *
+     * @param rng Source of randomness.
+     * @param array Array whose entries will be shuffled (in-place).
+     * @return a reference to the given array
+     */
+    static int[] shuffle2a(UniformRandomProvider rng, int[] array) {
+        int i = array.length;
+        // The threshold provided in the Brackett-Rozinsky and Lemire paper
+        // is the power of 2 below 20724. Note that the product 2^15*2^15
+        // is representable using signed integers.
+        for (; i > POW_15; i--) {
+            swap(array, i - 1, rng.nextInt(i));
+        }
+        // Batches of 2 for sizes up to 2^15 elements
+        final int[] indices = new int[2];
+        int bound = i * (i - 1);
+        for (; i > 1; i -= 2) {
+            bound = randomBounded2a(i, i - 1, bound, rng, indices);
+            swap(array, i - 1, indices[0]);
+            swap(array, i - 2, indices[1]);
+        }
+        return array;
+    }
+
     /**
      * Return three random values in {@code [0, range1)}, {@code [0, range2)}
      * and {@code [0, range3)}. The
@@ -362,7 +469,7 @@ public class ArrayShuffleBenchmark {
             productBound[0] = bound;
             if (l < bound) {
                 // 2^32 % bound
-                long t = POW_32 % bound;
+                final long t = POW_32 % bound;
                 while (l < t) {
                     m = (rng.nextInt() & MASK_32) * range1;
                     r1 = m;
@@ -455,7 +562,7 @@ public class ArrayShuffleBenchmark {
             productBound[0] = bound;
             if (l < bound) {
                 // 2^32 % bound
-                long t = POW_32 % bound;
+                final long t = POW_32 % bound;
                 while (l < t) {
                     m = (rng.nextInt() & MASK_32) * range1;
                     r1 = m;
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 7a105824..39c498c5 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
@@ -66,6 +66,29 @@ class ArrayShuffleBenchmarkTest {
         Assertions.assertFalse(p < 1e-3, () -> "p-value too small: " + p);
     }
 
+    @ParameterizedTest
+    @CsvSource({
+        "13, 12257",
+        "4242, 9899",
+    })
+    void testBoundedRandom2a(int range1, int range2) {
+        Assertions.assertTrue((long) range1 * range2 < 1L << 31, "Product must 
be less than 2^31");
+
+        final int samples = 1000;
+        final UniformRandomProvider rng1 = 
RandomSource.XO_SHI_RO_128_PP.create(SEED);
+        final UniformRandomProvider rng2 = 
RandomSource.XO_SHI_RO_128_PP.create(SEED);
+        final int[] productBound = {range1 * range2};
+        int bound = productBound[0];
+        final int[] indices2 = new int[2];
+        for (int i = 0; i < samples; i++) {
+            final int[] indices1 = 
ArrayShuffleBenchmark.randomBounded2(range1, range2, productBound, rng1);
+            bound = ArrayShuffleBenchmark.randomBounded2a(range1, range2, 
bound, rng2, indices2);
+            Assertions.assertEquals(indices1[0], indices2[0], "index0");
+            Assertions.assertEquals(indices1[1], indices2[1], "index1");
+            Assertions.assertEquals(productBound[0], bound, "bound");
+        }
+    }
+
     @ParameterizedTest
     @CsvSource({
         "257",
@@ -145,4 +168,28 @@ class ArrayShuffleBenchmarkTest {
             Assertions.assertArrayEquals(a, b);
         }
     }
+
+    @ParameterizedTest
+    @CsvSource({
+        "257",
+        "8073",
+    })
+    void testShuffle2a(int length) {
+        final int[] a = PermutationSampler.natural(length);
+        final int[] b = a.clone();
+        final int[] c = 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 UniformRandomProvider rng3 = source.create(seed);
+        final int samples = 10;
+        for (int j = 0; j < samples; j++) {
+            ArrayShuffleBenchmark.shuffle2(rng1, a);
+            ArrayShuffleBenchmark.shuffle2a(rng2, b);
+            ArrayShuffleBenchmark.shuffle2(rng3, c, 0, c.length);
+            Assertions.assertArrayEquals(a, b, "shuffle2a");
+            Assertions.assertArrayEquals(a, c, "shuffle2 range");
+        }
+    }
 }

Reply via email to