1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.rng.examples.jmh.sampling.distribution;
19
20 import org.apache.commons.rng.UniformRandomProvider;
21 import org.apache.commons.rng.sampling.distribution.DiscreteInverseCumulativeProbabilityFunction;
22 import org.apache.commons.rng.sampling.distribution.DiscreteSampler;
23 import org.apache.commons.rng.sampling.distribution.GeometricSampler;
24 import org.apache.commons.rng.sampling.distribution.InverseTransformDiscreteSampler;
25 import org.apache.commons.rng.simple.RandomSource;
26 import org.openjdk.jmh.annotations.Benchmark;
27 import org.openjdk.jmh.annotations.BenchmarkMode;
28 import org.openjdk.jmh.annotations.Fork;
29 import org.openjdk.jmh.annotations.Measurement;
30 import org.openjdk.jmh.annotations.Mode;
31 import org.openjdk.jmh.annotations.OutputTimeUnit;
32 import org.openjdk.jmh.annotations.Param;
33 import org.openjdk.jmh.annotations.Scope;
34 import org.openjdk.jmh.annotations.Setup;
35 import org.openjdk.jmh.annotations.State;
36 import org.openjdk.jmh.annotations.Warmup;
37
38 import java.util.concurrent.TimeUnit;
39
40 /**
41 * Executes a benchmark to compare the speed of generation of Geometric random numbers
42 * using different methods.
43 */
44 @BenchmarkMode(Mode.AverageTime)
45 @OutputTimeUnit(TimeUnit.NANOSECONDS)
46 @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
47 @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
48 @State(Scope.Benchmark)
49 @Fork(value = 1, jvmArgs = {"-server", "-Xms128M", "-Xmx128M"})
50 public class GeometricSamplersPerformance {
51 /**
52 * The value.
53 *
54 * <p>This must NOT be final!</p>
55 */
56 private int value;
57
58 /**
59 * The samplers's to use for testing. Defines the RandomSource, probability of success
60 * and the type of Geometric sampler.
61 */
62 @State(Scope.Benchmark)
63 public static class Sources {
64 /**
65 * RNG providers.
66 *
67 * <p>Use different speeds.</p>
68 *
69 * @see <a href="https://commons.apache.org/proper/commons-rng/userguide/rng.html">
70 * Commons RNG user guide</a>
71 */
72 @Param({"SPLIT_MIX_64",
73 "MWC_256",
74 "JDK"})
75 private String randomSourceName;
76
77 /**
78 * The probability of success.
79 */
80 @Param({"0.1", "0.3"})
81 private double probabilityOfSuccess;
82
83 /**
84 * The sampler type.
85 */
86 @Param({"GeometricSampler", "InverseTransformDiscreteSampler"})
87 private String samplerType;
88
89 /** The sampler. */
90 private DiscreteSampler sampler;
91
92 /**
93 * @return the sampler.
94 */
95 public DiscreteSampler getSampler() {
96 return sampler;
97 }
98
99 /** Instantiates sampler. */
100 @Setup
101 public void setup() {
102 final RandomSource randomSource = RandomSource.valueOf(randomSourceName);
103 final UniformRandomProvider rng = randomSource.create();
104 if ("GeometricSampler".equals(samplerType)) {
105 sampler = GeometricSampler.of(rng, probabilityOfSuccess);
106 } else {
107 final DiscreteInverseCumulativeProbabilityFunction geometricFunction =
108 new GeometricDiscreteInverseCumulativeProbabilityFunction(probabilityOfSuccess);
109 sampler = InverseTransformDiscreteSampler.of(rng, geometricFunction);
110 }
111 }
112 }
113
114 /**
115 * Define the inverse cumulative probability function for the Geometric distribution.
116 *
117 * <p>Adapted from org.apache.commons.math3.distribution.GeometricDistribution.
118 */
119 private static class GeometricDiscreteInverseCumulativeProbabilityFunction
120 implements DiscreteInverseCumulativeProbabilityFunction {
121 /**
122 * {@code log(1 - p)} where p is the probability of success.
123 */
124 private final double log1mProbabilityOfSuccess;
125
126 /**
127 * @param probabilityOfSuccess the probability of success
128 */
129 GeometricDiscreteInverseCumulativeProbabilityFunction(double probabilityOfSuccess) {
130 // No validation that 0 < p <= 1
131 log1mProbabilityOfSuccess = Math.log1p(-probabilityOfSuccess);
132 }
133
134 @Override
135 public int inverseCumulativeProbability(double cumulativeProbability) {
136 // This is the equivalent of floor(log(u)/ln(1-p))
137 // where:
138 // u = cumulative probability
139 // p = probability of success
140 // See: https://en.wikipedia.org/wiki/Geometric_distribution#Related_distributions
141 // ---
142 // Note: if cumulativeProbability == 0 then log1p(-0) is zero and the result
143 // after the range check is 0.
144 // Note: if cumulativeProbability == 1 then log1p(-1) is negative infinity, the result
145 // of the divide is positive infinity and the result after the range check is
146 // Integer.MAX_VALUE.
147 return Math.max(0, (int) Math.ceil(Math.log1p(-cumulativeProbability) / log1mProbabilityOfSuccess - 1));
148 }
149 }
150
151 /**
152 * Baseline for the JMH timing overhead for production of an {@code int} value.
153 *
154 * @return the {@code int} value
155 */
156 @Benchmark
157 public int baseline() {
158 return value;
159 }
160
161 /**
162 * Run the sampler.
163 *
164 * @param sources Source of randomness.
165 * @return the sample value
166 */
167 @Benchmark
168 public int sample(Sources sources) {
169 return sources.getSampler().sample();
170 }
171 }