001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.apache.commons.rng.examples.jmh.core;
019
020import java.util.function.IntSupplier;
021import org.apache.commons.rng.UniformRandomProvider;
022import org.apache.commons.rng.core.source64.LongProvider;
023import org.apache.commons.rng.examples.jmh.RandomSources;
024import org.apache.commons.rng.simple.RandomSource;
025import org.openjdk.jmh.annotations.Benchmark;
026import org.openjdk.jmh.annotations.Param;
027import org.openjdk.jmh.annotations.Scope;
028import org.openjdk.jmh.annotations.Setup;
029import org.openjdk.jmh.annotations.State;
030
031/**
032 * Executes a benchmark to compare the speed of generation of random numbers from the
033 * various source providers using the bit cache verses simple generation.
034 */
035public class CachedNextGenerationPerformance extends AbstractBenchmark {
036    /** The value. Must NOT be final to prevent JVM optimisation! */
037    private boolean booleanValue;
038    /** The value. Must NOT be final to prevent JVM optimisation! */
039    private int intValue;
040
041    /**
042     * Provides a function to obtain a boolean value from the various "RandomSource"s.
043     * It exercise the default nextBoolean() method (which may use
044     * a cache of bits) against a sign test on the native output.
045     */
046    @State(Scope.Benchmark)
047    public static class BooleanSources extends RandomSources {
048        /** Functional interface for a boolean generator. */
049        interface BooleanSupplier {
050            /**
051             * @return the boolean
052             */
053            boolean getAsBoolean();
054        }
055
056        /**
057         * The method to create the boolean value.
058         */
059        @Param({"nextBoolean", "signTest"})
060        private String method;
061
062        /** The generator. */
063        private BooleanSupplier generator;
064
065        /**
066         * @return the next boolean
067         */
068        boolean next() {
069            return generator.getAsBoolean();
070        }
071
072        /** {@inheritDoc} */
073        @Override
074        @Setup
075        public void setup() {
076            // Create the generator.
077            super.setup();
078            final UniformRandomProvider rng = getGenerator();
079
080            // Create the method to generate the boolean
081            if ("signTest".equals(method)) {
082                if (rng instanceof LongProvider) {
083                    generator = () -> rng.nextLong() < 0;
084                } else {
085                    // Assumed IntProvider
086                    generator = () -> rng.nextInt() < 0;
087                }
088            } else if ("nextBoolean".equals(method)) {
089                // Do not use a method handle 'rng::nextBoolean' for the nextBoolean
090                // to attempt to maintain a comparable lambda function. The JVM may
091                // optimise this away.
092                generator = () -> rng.nextBoolean();
093            } else {
094                throw new IllegalStateException("Unknown boolean method: " + method);
095            }
096        }
097    }
098    /**
099     * Provides a function to obtain an int value from the various "RandomSource"s
100     * that produce 64-bit output.
101     * It exercise the default nextInt() method (which may use
102     * a cache of bits) against a shift on the native output.
103     */
104    @State(Scope.Benchmark)
105    public static class IntSources {
106        /**
107         * RNG providers. This list is maintained in the order of the {@link RandomSource} enum.
108         *
109         * <p>Include only those that are a {@link LongProvider}.</p>
110         */
111        @Param({"SPLIT_MIX_64",
112                "XOR_SHIFT_1024_S",
113                "TWO_CMRES",
114                "MT_64",
115                "XOR_SHIFT_1024_S_PHI",
116                "XO_RO_SHI_RO_128_PLUS",
117                "XO_RO_SHI_RO_128_SS",
118                "XO_SHI_RO_256_PLUS",
119                "XO_SHI_RO_256_SS",
120                "XO_SHI_RO_512_PLUS",
121                "XO_SHI_RO_512_SS",
122                "PCG_RXS_M_XS_64",
123                "SFC_64",
124                "JSF_64",
125                "XO_RO_SHI_RO_128_PP",
126                "XO_SHI_RO_256_PP",
127                "XO_SHI_RO_512_PP",
128                "XO_RO_SHI_RO_1024_PP",
129                "XO_RO_SHI_RO_1024_S",
130                "XO_RO_SHI_RO_1024_SS",
131                "PCG_RXS_M_XS_64_OS"})
132        private String randomSourceName;
133
134        /**
135         * The method to create the int value.
136         */
137        @Param({"nextInt", "shiftLong"})
138        private String method;
139
140        /** The generator. */
141        private IntSupplier gen;
142
143        /**
144         * @return the next int
145         */
146        int next() {
147            return gen.getAsInt();
148        }
149
150        /** Create the int source. */
151        @Setup
152        public void setup() {
153            final UniformRandomProvider rng = RandomSource.valueOf(randomSourceName).create();
154            if (!(rng instanceof LongProvider)) {
155                throw new IllegalStateException("Not a LongProvider: " + rng.getClass().getName());
156            }
157
158            // Create the method to generate the int
159            if ("shiftLong".equals(method)) {
160                gen = () -> (int) (rng.nextLong() >>> 32);
161            } else if ("nextInt".equals(method)) {
162                // Do not use a method handle 'rng::nextInt' for the nextInt
163                // to attempt to maintain a comparable lambda function. The JVM may
164                // optimise this away.
165                gen = () -> rng.nextInt();
166            } else {
167                throw new IllegalStateException("Unknown int method: " + method);
168            }
169        }
170    }
171
172    /**
173     * Baseline for a JMH method call with no return value.
174     */
175    @Benchmark
176    public void baselineVoid() {
177        // Do nothing, this is a baseline
178    }
179
180    /**
181     * Baseline for a JMH method call returning a {@code boolean}.
182     *
183     * @return the value
184     */
185    @Benchmark
186    public boolean baselineBoolean() {
187        return booleanValue;
188    }
189
190    /**
191     * Baseline for a JMH method call returning an {@code int}.
192     *
193     * @return the value
194     */
195    @Benchmark
196    public int baselineInt() {
197        return intValue;
198    }
199
200    /**
201     * Exercise the boolean generation method.
202     *
203     * @param sources Source of randomness.
204     * @return the boolean
205     */
206    @Benchmark
207    public boolean nextBoolean(BooleanSources sources) {
208        return sources.next();
209    }
210
211    /**
212     * Exercise the int generation method.
213     *
214     * @param sources Source of randomness.
215     * @return the int
216     */
217    @Benchmark
218    public int nextInt(IntSources sources) {
219        return sources.next();
220    }
221}