View Javadoc
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;
19  
20  import org.apache.commons.rng.UniformRandomProvider;
21  import org.apache.commons.rng.sampling.ObjectSampler;
22  import org.apache.commons.rng.sampling.distribution.NormalizedGaussianSampler;
23  import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
24  import org.apache.commons.rng.simple.RandomSource;
25  import org.openjdk.jmh.annotations.Benchmark;
26  import org.openjdk.jmh.annotations.BenchmarkMode;
27  import org.openjdk.jmh.annotations.Fork;
28  import org.openjdk.jmh.annotations.Measurement;
29  import org.openjdk.jmh.annotations.Mode;
30  import org.openjdk.jmh.annotations.OutputTimeUnit;
31  import org.openjdk.jmh.annotations.Param;
32  import org.openjdk.jmh.annotations.Scope;
33  import org.openjdk.jmh.annotations.Setup;
34  import org.openjdk.jmh.annotations.State;
35  import org.openjdk.jmh.annotations.Warmup;
36  import org.openjdk.jmh.infra.Blackhole;
37  import java.util.concurrent.TimeUnit;
38  
39  /**
40   * Executes benchmark to compare the speed of generating samples on the surface of an
41   * N-dimension unit sphere.
42   */
43  @BenchmarkMode(Mode.AverageTime)
44  @OutputTimeUnit(TimeUnit.NANOSECONDS)
45  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
46  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
47  @State(Scope.Benchmark)
48  @Fork(value = 1, jvmArgs = { "-server", "-Xms128M", "-Xmx128M" })
49  public class UnitSphereSamplerBenchmark {
50      /** Name for the baseline method. */
51      private static final String BASELINE = "Baseline";
52      /** Name for the non-array based method. */
53      private static final String NON_ARRAY = "NonArray";
54      /** Name for the array based method. */
55      private static final String ARRAY = "Array";
56      /** Error message for an unknown sampler type. */
57      private static final String UNKNOWN_SAMPLER = "Unknown sampler type: ";
58  
59      /**
60       * Base class for the sampler data.
61       * Contains the source of randomness and the number of samples.
62       * The sampler should be created by a sub-class of the data.
63       */
64      @State(Scope.Benchmark)
65      public abstract static class SamplerData {
66          /** The sampler. */
67          private ObjectSampler<double[]> sampler;
68  
69          /** The number of samples. */
70          @Param({"100"})
71          private int size;
72  
73          /**
74           * Gets the size.
75           *
76           * @return the size
77           */
78          public int getSize() {
79              return size;
80          }
81  
82          /**
83           * Gets the sampler.
84           *
85           * @return the sampler
86           */
87          public ObjectSampler<double[]> getSampler() {
88              return sampler;
89          }
90  
91          /**
92           * Create the source of randomness.
93           */
94          @Setup
95          public void setup() {
96              // This could be configured using @Param
97              final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
98              sampler = createSampler(rng);
99          }
100 
101         /**
102          * Creates the sampler.
103          *
104          * @param rng the source of randomness
105          * @return the sampler
106          */
107         protected abstract ObjectSampler<double[]> createSampler(UniformRandomProvider rng);
108     }
109 
110     /**
111      * The 1D unit line sampler.
112      */
113     @State(Scope.Benchmark)
114     public static class Sampler1D extends SamplerData {
115         /** Name for the signed double method. */
116         private static final String SIGNED_DOUBLE = "signedDouble";
117         /** Name for the masked int method. */
118         private static final String MASKED_INT = "maskedInt";
119         /** Name for the masked long method. */
120         private static final String MASKED_LONG = "maskedLong";
121         /** Name for the boolean method. */
122         private static final String BOOLEAN = "boolean";
123         /** The value 1.0 in raw long bits. */
124         private static final long ONE = Double.doubleToRawLongBits(1.0);
125         /** Mask to extract the sign bit from an integer (as a long). */
126         private static final long SIGN_BIT = 1L << 31;
127 
128         /** The sampler type. */
129         @Param({BASELINE, SIGNED_DOUBLE, MASKED_INT, MASKED_LONG, BOOLEAN, ARRAY})
130         private String type;
131 
132         /** {@inheritDoc} */
133         @Override
134         protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
135             if (BASELINE.equals(type)) {
136                 return () -> {
137                     return new double[] {1.0};
138                 };
139             } else if (SIGNED_DOUBLE.equals(type)) {
140                 return () -> {
141                     // (1 - 0) or (1 - 2)
142                     // Use the sign bit
143                     return new double[] {1.0 - ((rng.nextInt() >>> 30) & 0x2)};
144                 };
145             } else if (MASKED_INT.equals(type)) {
146                 return () -> {
147                     // Shift the sign bit and combine with the bits for a double of 1.0
148                     return new double[] {Double.longBitsToDouble(ONE | ((rng.nextInt() & SIGN_BIT) << 32))};
149                 };
150             } else if (MASKED_LONG.equals(type)) {
151                 return () -> {
152                     // Combine the sign bit with the bits for a double of 1.0
153                     return new double[] {Double.longBitsToDouble(ONE | (rng.nextLong() & Long.MIN_VALUE))};
154                 };
155             } else if (BOOLEAN.equals(type)) {
156                 return () -> {
157                     return new double[] {rng.nextBoolean() ? -1.0 : 1.0};
158                 };
159             } else if (ARRAY.equals(type)) {
160                 return new ArrayBasedUnitSphereSampler(1, rng);
161             }
162             throw new IllegalStateException(UNKNOWN_SAMPLER + type);
163         }
164     }
165 
166     /**
167      * The 2D unit circle sampler.
168      */
169     @State(Scope.Benchmark)
170     public static class Sampler2D extends SamplerData {
171         /** The sampler type. */
172         @Param({BASELINE, ARRAY, NON_ARRAY})
173         private String type;
174 
175         /** {@inheritDoc} */
176         @Override
177         protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
178             if (BASELINE.equals(type)) {
179                 return () -> new double[] {1.0, 0.0};
180             } else if (ARRAY.equals(type)) {
181                 return new ArrayBasedUnitSphereSampler(2, rng);
182             } else if (NON_ARRAY.equals(type)) {
183                 return new UnitSphereSampler2D(rng);
184             }
185             throw new IllegalStateException(UNKNOWN_SAMPLER + type);
186         }
187 
188         /**
189          * Sample from a 2D unit sphere.
190          */
191         private static class UnitSphereSampler2D implements ObjectSampler<double[]> {
192             /** Sampler used for generating the individual components of the vectors. */
193             private final NormalizedGaussianSampler sampler;
194 
195             /**
196              * @param rng the source of randomness
197              */
198             UnitSphereSampler2D(UniformRandomProvider rng) {
199                 sampler = ZigguratSampler.NormalizedGaussian.of(rng);
200             }
201 
202             @Override
203             public double[] sample() {
204                 final double x = sampler.sample();
205                 final double y = sampler.sample();
206                 final double sum = x * x + y * y;
207 
208                 if (sum == 0) {
209                     // Zero-norm vector is discarded.
210                     return sample();
211                 }
212 
213                 final double f = 1.0 / Math.sqrt(sum);
214                 return new double[] {x * f, y * f};
215             }
216         }
217     }
218 
219     /**
220      * The 3D unit sphere sampler.
221      */
222     @State(Scope.Benchmark)
223     public static class Sampler3D extends SamplerData {
224         /** The sampler type. */
225         @Param({BASELINE, ARRAY, NON_ARRAY})
226         private String type;
227 
228         /** {@inheritDoc} */
229         @Override
230         protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
231             if (BASELINE.equals(type)) {
232                 return () -> new double[] {1.0, 0.0, 0.0};
233             } else if (ARRAY.equals(type)) {
234                 return new ArrayBasedUnitSphereSampler(3, rng);
235             } else if (NON_ARRAY.equals(type)) {
236                 return new UnitSphereSampler3D(rng);
237             }
238             throw new IllegalStateException(UNKNOWN_SAMPLER + type);
239         }
240 
241         /**
242          * Sample from a 3D unit sphere.
243          */
244         private static class UnitSphereSampler3D implements ObjectSampler<double[]> {
245             /** Sampler used for generating the individual components of the vectors. */
246             private final NormalizedGaussianSampler sampler;
247 
248             /**
249              * @param rng the source of randomness
250              */
251             UnitSphereSampler3D(UniformRandomProvider rng) {
252                 sampler = ZigguratSampler.NormalizedGaussian.of(rng);
253             }
254 
255             @Override
256             public double[] sample() {
257                 final double x = sampler.sample();
258                 final double y = sampler.sample();
259                 final double z = sampler.sample();
260                 final double sum = x * x + y * y + z * z;
261 
262                 if (sum == 0) {
263                     // Zero-norm vector is discarded.
264                     return sample();
265                 }
266 
267                 final double f = 1.0 / Math.sqrt(sum);
268                 return new double[] {x * f, y * f, z * f};
269             }
270         }
271     }
272 
273     /**
274      * The 4D unit hypersphere sampler.
275      */
276     @State(Scope.Benchmark)
277     public static class Sampler4D extends SamplerData {
278         /** The sampler type. */
279         @Param({BASELINE, ARRAY, NON_ARRAY})
280         private String type;
281 
282         /** {@inheritDoc} */
283         @Override
284         protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
285             if (BASELINE.equals(type)) {
286                 return () -> new double[] {1.0, 0.0, 0.0, 0.0};
287             } else if (ARRAY.equals(type)) {
288                 return new ArrayBasedUnitSphereSampler(4, rng);
289             } else if (NON_ARRAY.equals(type)) {
290                 return new UnitSphereSampler4D(rng);
291             }
292             throw new IllegalStateException(UNKNOWN_SAMPLER + type);
293         }
294 
295         /**
296          * Sample from a 4D unit hypersphere.
297          */
298         private static class UnitSphereSampler4D implements ObjectSampler<double[]> {
299             /** Sampler used for generating the individual components of the vectors. */
300             private final NormalizedGaussianSampler sampler;
301 
302             /**
303              * @param rng the source of randomness
304              */
305             UnitSphereSampler4D(UniformRandomProvider rng) {
306                 sampler = ZigguratSampler.NormalizedGaussian.of(rng);
307             }
308 
309             @Override
310             public double[] sample() {
311                 final double x = sampler.sample();
312                 final double y = sampler.sample();
313                 final double z = sampler.sample();
314                 final double a = sampler.sample();
315                 final double sum = x * x + y * y + z * z + a * a;
316 
317                 if (sum == 0) {
318                     // Zero-norm vector is discarded.
319                     return sample();
320                 }
321 
322                 final double f = 1.0 / Math.sqrt(sum);
323                 return new double[] {x * f, y * f, z * f, a * f};
324             }
325         }
326     }
327 
328     /**
329      * Sample from a unit sphere using an array based method.
330      */
331     private static class ArrayBasedUnitSphereSampler implements ObjectSampler<double[]> {
332         /** Space dimension. */
333         private final int dimension;
334         /** Sampler used for generating the individual components of the vectors. */
335         private final NormalizedGaussianSampler sampler;
336 
337         /**
338          * @param dimension space dimension
339          * @param rng the source of randomness
340          */
341         ArrayBasedUnitSphereSampler(int dimension, UniformRandomProvider rng) {
342             this.dimension = dimension;
343             sampler = ZigguratSampler.NormalizedGaussian.of(rng);
344         }
345 
346         @Override
347         public double[] sample() {
348             final double[] v = new double[dimension];
349 
350             // Pick a point by choosing a standard Gaussian for each element,
351             // and then normalize to unit length.
352             double sum = 0;
353             for (int i = 0; i < dimension; i++) {
354                 final double x = sampler.sample();
355                 v[i] = x;
356                 sum += x * x;
357             }
358 
359             if (sum == 0) {
360                 // Zero-norm vector is discarded.
361                 return sample();
362             }
363 
364             final double f = 1 / Math.sqrt(sum);
365             for (int i = 0; i < dimension; i++) {
366                 v[i] *= f;
367             }
368 
369             return v;
370         }
371     }
372 
373     /**
374      * Run the sampler for the configured number of samples.
375      *
376      * @param bh Data sink
377      * @param data Input data.
378      */
379     private static void runSampler(Blackhole bh, SamplerData data) {
380         final ObjectSampler<double[]> sampler = data.getSampler();
381         for (int i = data.getSize() - 1; i >= 0; i--) {
382             bh.consume(sampler.sample());
383         }
384     }
385 
386     /**
387      * Generation of uniform samples on a 1D unit line.
388      *
389      * @param bh Data sink
390      * @param data Input data.
391      */
392     @Benchmark
393     public void create1D(Blackhole bh, Sampler1D data) {
394         runSampler(bh, data);
395     }
396 
397     /**
398      * Generation of uniform samples from a 2D unit circle.
399      *
400      * @param bh Data sink
401      * @param data Input data.
402      */
403     @Benchmark
404     public void create2D(Blackhole bh, Sampler2D data) {
405         runSampler(bh, data);
406     }
407 
408     /**
409      * Generation of uniform samples from a 3D unit sphere.
410      *
411      * @param bh Data sink
412      * @param data Input data.
413      */
414     @Benchmark
415     public void create3D(Blackhole bh, Sampler3D data) {
416         runSampler(bh, data);
417     }
418 
419     /**
420      * Generation of uniform samples from a 4D unit sphere.
421      *
422      * @param bh Data sink
423      * @param data Input data.
424      */
425     @Benchmark
426     public void create4D(Blackhole bh, Sampler4D data) {
427         runSampler(bh, data);
428     }
429 }