1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.rng.examples.jmh.sampling.shape;
19
20 import org.apache.commons.rng.UniformRandomProvider;
21 import org.apache.commons.rng.sampling.ObjectSampler;
22 import org.apache.commons.rng.sampling.distribution.ContinuousSampler;
23 import org.apache.commons.rng.sampling.distribution.NormalizedGaussianSampler;
24 import org.apache.commons.rng.sampling.distribution.ZigguratSampler;
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 import org.openjdk.jmh.infra.Blackhole;
38 import java.util.concurrent.TimeUnit;
39
40
41
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 UnitBallSamplerBenchmark {
50
51 private static final String BASELINE = "Baseline";
52
53 private static final String REJECTION = "Rejection";
54
55 private static final String DISK_POINT = "DiskPoint";
56
57 private static final String BALL_POINT = "BallPoint";
58
59 private static final String HYPERSPHERE_INTERNAL = "HypersphereInternal";
60
61 private static final String HYPERSPHERE_DISCARD = "HypersphereDiscard";
62
63 private static final String UNKNOWN_SAMPLER = "Unknown sampler type: ";
64
65 private static final long LOWER_53_BITS = -1L >>> 11;
66
67
68
69
70 private abstract static class BaseSampler implements ObjectSampler<double[]> {
71
72 protected UniformRandomProvider rng;
73
74
75
76
77
78
79 BaseSampler(UniformRandomProvider rng) {
80 this.rng = rng;
81 }
82 }
83
84
85
86
87
88
89 @State(Scope.Benchmark)
90 public abstract static class SamplerData {
91
92 private ObjectSampler<double[]> sampler;
93
94
95 @Param({
96 "100"})
97 private int size;
98
99
100
101
102
103
104 public int getSize() {
105 return size;
106 }
107
108
109
110
111
112
113 public ObjectSampler<double[]> getSampler() {
114 return sampler;
115 }
116
117
118
119
120 @Setup
121 public void setup() {
122
123 final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_128_PP.create();
124 sampler = createSampler(rng);
125 }
126
127
128
129
130
131
132
133 protected abstract ObjectSampler<double[]> createSampler(UniformRandomProvider rng);
134 }
135
136
137
138
139 @State(Scope.Benchmark)
140 public static class Sampler1D extends SamplerData {
141
142 private static final String SIGNED_DOUBLE = "signedDouble";
143
144 private static final String SIGNED_DOUBLE2 = "signedDouble2";
145
146 private static final String TWO_DOUBLES = "twoDoubles";
147
148 private static final String BOOLEAN_DOUBLE = "booleanDouble";
149
150
151 @Param({BASELINE, SIGNED_DOUBLE, SIGNED_DOUBLE2, TWO_DOUBLES, BOOLEAN_DOUBLE})
152 private String type;
153
154
155 @Override
156 protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
157 if (BASELINE.equals(type)) {
158 return () -> new double[] {0.5};
159 } else if (SIGNED_DOUBLE.equals(type)) {
160
161 return () -> new double[] {makeSignedDouble(rng.nextLong())};
162 } else if (SIGNED_DOUBLE2.equals(type)) {
163
164 return () -> new double[] {makeSignedDouble2(rng.nextLong())};
165 } else if (TWO_DOUBLES.equals(type)) {
166
167
168 return () -> new double[] {rng.nextDouble() + rng.nextDouble() - 1.0};
169 } else if (BOOLEAN_DOUBLE.equals(type)) {
170
171 return () -> new double[] {rng.nextBoolean() ? -rng.nextDouble() : rng.nextDouble()};
172 }
173 throw new IllegalStateException(UNKNOWN_SAMPLER + type);
174 }
175 }
176
177
178
179
180 @State(Scope.Benchmark)
181 public static class Sampler2D extends SamplerData {
182
183 @Param({BASELINE, REJECTION, DISK_POINT, HYPERSPHERE_INTERNAL, HYPERSPHERE_DISCARD})
184 private String type;
185
186
187 @Override
188 protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
189 if (BASELINE.equals(type)) {
190 return () -> new double[] {0.5, 0};
191 } else if (REJECTION.equals(type)) {
192 return new RejectionSampler(rng);
193 } else if (DISK_POINT.equals(type)) {
194 return new DiskPointSampler(rng);
195 } else if (HYPERSPHERE_INTERNAL.equals(type)) {
196 return new HypersphereInternalSampler(rng);
197 } else if (HYPERSPHERE_DISCARD.equals(type)) {
198 return new HypersphereDiscardSampler(rng);
199 }
200 throw new IllegalStateException(UNKNOWN_SAMPLER + type);
201 }
202
203
204
205
206 private static class RejectionSampler extends BaseSampler {
207
208
209
210 RejectionSampler(UniformRandomProvider rng) {
211 super(rng);
212 }
213
214 @Override
215 public double[] sample() {
216
217
218 double x;
219 double y;
220 do {
221 x = makeSignedDouble(rng.nextLong());
222 y = makeSignedDouble(rng.nextLong());
223 } while (x * x + y * y > 1.0);
224 return new double[] {x, y};
225 }
226 }
227
228
229
230
231
232 private static class DiskPointSampler extends BaseSampler {
233
234 private static final double TWO_PI = 2 * Math.PI;
235
236
237
238
239 DiskPointSampler(UniformRandomProvider rng) {
240 super(rng);
241 }
242
243 @Override
244 public double[] sample() {
245 final double t = TWO_PI * rng.nextDouble();
246 final double r = Math.sqrt(rng.nextDouble());
247 final double x = r * Math.cos(t);
248 final double y = r * Math.sin(t);
249 return new double[] {x, y};
250 }
251 }
252
253
254
255
256
257
258
259 private static class HypersphereInternalSampler extends BaseSampler {
260
261 private final NormalizedGaussianSampler normal;
262
263
264
265
266 HypersphereInternalSampler(UniformRandomProvider rng) {
267 super(rng);
268 normal = ZigguratSampler.NormalizedGaussian.of(rng);
269 }
270
271 @Override
272 public double[] sample() {
273 final double x = normal.sample();
274 final double y = normal.sample();
275 final double sum = x * x + y * y;
276
277 if (sum == 0) {
278 return sample();
279 }
280
281 final double f = Math.sqrt(rng.nextDouble()) / Math.sqrt(sum);
282 return new double[] {x * f, y * f};
283 }
284 }
285
286
287
288
289
290
291
292 private static class HypersphereDiscardSampler extends BaseSampler {
293
294 private final NormalizedGaussianSampler normal;
295
296
297
298
299 HypersphereDiscardSampler(UniformRandomProvider rng) {
300 super(rng);
301 normal = ZigguratSampler.NormalizedGaussian.of(rng);
302 }
303
304 @Override
305 public double[] sample() {
306
307 final double x0 = normal.sample();
308 final double x1 = normal.sample();
309 final double x = normal.sample();
310 final double y = normal.sample();
311 final double sum = x0 * x0 + x1 * x1 + x * x + y * y;
312
313 if (sum == 0) {
314 return sample();
315 }
316 final double f = 1.0 / Math.sqrt(sum);
317 return new double[] {x * f, y * f};
318 }
319 }
320 }
321
322
323
324
325 @State(Scope.Benchmark)
326 public static class Sampler3D extends SamplerData {
327
328 @Param({BASELINE, REJECTION, BALL_POINT, HYPERSPHERE_INTERNAL, HYPERSPHERE_DISCARD})
329 private String type;
330
331
332 @Override
333 protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
334 if (BASELINE.equals(type)) {
335 return () -> new double[] {0.5, 0, 0};
336 } else if (REJECTION.equals(type)) {
337 return new RejectionSampler(rng);
338 } else if (BALL_POINT.equals(type)) {
339 return new BallPointSampler(rng);
340 } else if (HYPERSPHERE_INTERNAL.equals(type)) {
341 return new HypersphereInternalSampler(rng);
342 } else if (HYPERSPHERE_DISCARD.equals(type)) {
343 return new HypersphereDiscardSampler(rng);
344 }
345 throw new IllegalStateException(UNKNOWN_SAMPLER + type);
346 }
347
348
349
350
351 private static class RejectionSampler extends BaseSampler {
352
353
354
355 RejectionSampler(UniformRandomProvider rng) {
356 super(rng);
357 }
358
359 @Override
360 public double[] sample() {
361
362
363 double x;
364 double y;
365 double z;
366 do {
367 x = makeSignedDouble(rng.nextLong());
368 y = makeSignedDouble(rng.nextLong());
369 z = makeSignedDouble(rng.nextLong());
370 } while (x * x + y * y + z * z > 1.0);
371 return new double[] {x, y, z};
372 }
373 }
374
375
376
377
378
379 private static class BallPointSampler extends BaseSampler {
380
381 private final NormalizedGaussianSampler normal;
382
383 private final ContinuousSampler exp;
384
385
386
387
388 BallPointSampler(UniformRandomProvider rng) {
389 super(rng);
390 normal = ZigguratSampler.NormalizedGaussian.of(rng);
391
392
393
394 exp = ZigguratSampler.Exponential.of(rng);
395 }
396
397 @Override
398 public double[] sample() {
399 final double x = normal.sample();
400 final double y = normal.sample();
401 final double z = normal.sample();
402
403 final double sum = exp.sample() * 2 + x * x + y * y + z * z;
404
405 if (sum == 0) {
406 return sample();
407 }
408 final double f = 1.0 / Math.sqrt(sum);
409 return new double[] {x * f, y * f, z * f};
410 }
411 }
412
413
414
415
416
417
418
419 private static class HypersphereInternalSampler extends BaseSampler {
420
421 private final NormalizedGaussianSampler normal;
422
423
424
425
426 HypersphereInternalSampler(UniformRandomProvider rng) {
427 super(rng);
428 normal = ZigguratSampler.NormalizedGaussian.of(rng);
429 }
430
431 @Override
432 public double[] sample() {
433 final double x = normal.sample();
434 final double y = normal.sample();
435 final double z = normal.sample();
436 final double sum = x * x + y * y + z * z;
437
438 if (sum == 0) {
439 return sample();
440 }
441
442 final double f = Math.cbrt(rng.nextDouble()) / Math.sqrt(sum);
443 return new double[] {x * f, y * f, z * f};
444 }
445 }
446
447
448
449
450
451
452
453 private static class HypersphereDiscardSampler extends BaseSampler {
454
455 private final NormalizedGaussianSampler normal;
456
457
458
459
460 HypersphereDiscardSampler(UniformRandomProvider rng) {
461 super(rng);
462 normal = ZigguratSampler.NormalizedGaussian.of(rng);
463 }
464
465 @Override
466 public double[] sample() {
467
468 final double x0 = normal.sample();
469 final double x1 = normal.sample();
470 final double x = normal.sample();
471 final double y = normal.sample();
472 final double z = normal.sample();
473 final double sum = x0 * x0 + x1 * x1 + x * x + y * y + z * z;
474
475 if (sum == 0) {
476 return sample();
477 }
478 final double f = 1.0 / Math.sqrt(sum);
479 return new double[] {x * f, y * f, z * f};
480 }
481 }
482 }
483
484
485
486
487 @State(Scope.Benchmark)
488 public static class SamplerND extends SamplerData {
489
490 @Param({BASELINE, REJECTION, BALL_POINT, HYPERSPHERE_INTERNAL, HYPERSPHERE_DISCARD})
491 private String type;
492
493 @Param({"3", "4", "5"})
494 private int dimension;
495
496
497 @Override
498 protected ObjectSampler<double[]> createSampler(final UniformRandomProvider rng) {
499 if (BASELINE.equals(type)) {
500 return new ObjectSampler<double[]>() {
501 private final int dim = dimension;
502 @Override
503 public double[] sample() {
504 final double[] sample = new double[dim];
505 for (int i = 0; i < dim; i++) {
506 sample[i] = 0.01;
507 }
508 return sample;
509 }
510 };
511 } else if (REJECTION.equals(type)) {
512 return new RejectionSampler(rng, dimension);
513 } else if (BALL_POINT.equals(type)) {
514 return new BallPointSampler(rng, dimension);
515 } else if (HYPERSPHERE_INTERNAL.equals(type)) {
516 return new HypersphereInternalSampler(rng, dimension);
517 } else if (HYPERSPHERE_DISCARD.equals(type)) {
518 return new HypersphereDiscardSampler(rng, dimension);
519 }
520 throw new IllegalStateException(UNKNOWN_SAMPLER + type);
521 }
522
523
524
525
526 private static class RejectionSampler extends BaseSampler {
527
528 private final int dimension;
529
530
531
532
533
534 RejectionSampler(UniformRandomProvider rng, int dimension) {
535 super(rng);
536 this.dimension = dimension;
537 }
538
539 @Override
540 public double[] sample() {
541
542 final double[] sample = new double[dimension];
543 double sum;
544 do {
545 sum = 0;
546 for (int i = 0; i < dimension; i++) {
547 final double x = makeSignedDouble(rng.nextLong());
548 sum += x * x;
549 sample[i] = x;
550 }
551 } while (sum > 1.0);
552 return sample;
553 }
554 }
555
556
557
558
559
560 private static class BallPointSampler extends BaseSampler {
561
562 private final int dimension;
563
564 private final NormalizedGaussianSampler normal;
565
566 private final ContinuousSampler exp;
567
568
569
570
571
572 BallPointSampler(UniformRandomProvider rng, int dimension) {
573 super(rng);
574 this.dimension = dimension;
575 normal = ZigguratSampler.NormalizedGaussian.of(rng);
576
577
578
579 exp = ZigguratSampler.Exponential.of(rng);
580 }
581
582 @Override
583 public double[] sample() {
584 final double[] sample = new double[dimension];
585
586 double sum = exp.sample() * 2;
587 for (int i = 0; i < dimension; i++) {
588 final double x = normal.sample();
589 sum += x * x;
590 sample[i] = x;
591 }
592
593 if (sum == 0) {
594 return sample();
595 }
596 final double f = 1.0 / Math.sqrt(sum);
597 for (int i = 0; i < dimension; i++) {
598 sample[i] *= f;
599 }
600 return sample;
601 }
602 }
603
604
605
606
607
608
609
610 private static class HypersphereInternalSampler extends BaseSampler {
611
612 private final int dimension;
613
614 private final NormalizedGaussianSampler normal;
615
616 private final double power;
617
618
619
620
621
622 HypersphereInternalSampler(UniformRandomProvider rng, int dimension) {
623 super(rng);
624 this.dimension = dimension;
625 power = 1.0 / dimension;
626 normal = ZigguratSampler.NormalizedGaussian.of(rng);
627 }
628
629 @Override
630 public double[] sample() {
631 final double[] sample = new double[dimension];
632 double sum = 0;
633 for (int i = 0; i < dimension; i++) {
634 final double x = normal.sample();
635 sum += x * x;
636 sample[i] = x;
637 }
638
639 if (sum == 0) {
640 return sample();
641 }
642
643 final double f = Math.pow(rng.nextDouble(), power) / Math.sqrt(sum);
644 for (int i = 0; i < dimension; i++) {
645 sample[i] *= f;
646 }
647 return sample;
648 }
649 }
650
651
652
653
654
655
656
657 private static class HypersphereDiscardSampler extends BaseSampler {
658
659 private final int dimension;
660
661 private final NormalizedGaussianSampler normal;
662
663
664
665
666
667 HypersphereDiscardSampler(UniformRandomProvider rng, int dimension) {
668 super(rng);
669 this.dimension = dimension;
670 normal = ZigguratSampler.NormalizedGaussian.of(rng);
671 }
672
673 @Override
674 public double[] sample() {
675 final double[] sample = new double[dimension];
676
677 final double x0 = normal.sample();
678 final double x1 = normal.sample();
679 double sum = x0 * x0 + x1 * x1;
680 for (int i = 0; i < dimension; i++) {
681 final double x = normal.sample();
682 sum += x * x;
683 sample[i] = x;
684 }
685
686 if (sum == 0) {
687 return sample();
688 }
689 final double f = 1.0 / Math.sqrt(sum);
690 for (int i = 0; i < dimension; i++) {
691 sample[i] *= f;
692 }
693 return sample;
694 }
695 }
696 }
697
698
699
700
701
702
703
704
705
706
707 private static double makeSignedDouble(long bits) {
708
709
710 return (bits >> 10) * 0x1.0p-53d;
711 }
712
713
714
715
716
717
718
719
720
721
722 private static double makeSignedDouble2(long bits) {
723
724
725
726
727 return (((bits >>> 10) & LOWER_53_BITS) * 0x1.0p-53d) - (bits >>> 63);
728 }
729
730
731
732
733
734
735
736 private static void runSampler(Blackhole bh, SamplerData data) {
737 final ObjectSampler<double[]> sampler = data.getSampler();
738 for (int i = data.getSize() - 1; i >= 0; i--) {
739 bh.consume(sampler.sample());
740 }
741 }
742
743
744
745
746
747
748
749 @Benchmark
750 public void create1D(Blackhole bh, Sampler1D data) {
751 runSampler(bh, data);
752 }
753
754
755
756
757
758
759
760 @Benchmark
761 public void create2D(Blackhole bh, Sampler2D data) {
762 runSampler(bh, data);
763 }
764
765
766
767
768
769
770
771 @Benchmark
772 public void create3D(Blackhole bh, Sampler3D data) {
773 runSampler(bh, data);
774 }
775
776
777
778
779
780
781
782 @Benchmark
783 public void createND(Blackhole bh, SamplerND data) {
784 runSampler(bh, data);
785 }
786 }