1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.numbers.examples.jmh.core;
19
20 import java.util.concurrent.TimeUnit;
21 import java.util.function.DoubleBinaryOperator;
22 import java.util.function.DoublePredicate;
23 import java.util.function.DoubleUnaryOperator;
24
25 import org.apache.commons.rng.UniformRandomProvider;
26 import org.apache.commons.rng.simple.RandomSource;
27 import org.openjdk.jmh.annotations.Benchmark;
28 import org.openjdk.jmh.annotations.BenchmarkMode;
29 import org.openjdk.jmh.annotations.Fork;
30 import org.openjdk.jmh.annotations.Measurement;
31 import org.openjdk.jmh.annotations.Mode;
32 import org.openjdk.jmh.annotations.OutputTimeUnit;
33 import org.openjdk.jmh.annotations.Param;
34 import org.openjdk.jmh.annotations.Scope;
35 import org.openjdk.jmh.annotations.Setup;
36 import org.openjdk.jmh.annotations.State;
37 import org.openjdk.jmh.annotations.Warmup;
38 import org.openjdk.jmh.infra.Blackhole;
39
40
41
42
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", "-Xms512M", "-Xmx512M"})
50 public class DoubleSplitPerformance {
51
52 private static final long SIGN_MATISSA_MASK = 0x800f_ffff_ffff_ffffL;
53
54
55
56
57
58
59
60 private static final double MULTIPLIER = 1.34217729E8;
61
62
63
64
65 private static final double SAFE_UPPER = 0x1.0p996;
66
67
68
69 private static final double DOWN_SCALE = 0x1.0p-30;
70
71
72
73 private static final double UP_SCALE = 0x1.0p30;
74
75
76 private static final long ZERO_LOWER_27_BITS = 0xffff_ffff_f800_0000L;
77
78
79 private static final String NONE = "none";
80
81
82
83
84 @State(Scope.Benchmark)
85 public static class Numbers {
86
87 private static final long EXP_SMALL = Double.doubleToRawLongBits(1.0);
88
89 private static final long EXP_BIG = Double.doubleToRawLongBits(SAFE_UPPER);
90
91
92
93
94 @Param({"10000"})
95 private int size;
96
97
98
99
100
101
102
103 @Param({"1", "0.999", "0.99", "0.9"})
104 private double edge;
105
106
107 private double[] a;
108
109
110
111
112
113
114 public double[] getNumbers() {
115 return a;
116 }
117
118
119
120
121 @Setup
122 public void setup() {
123 final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_1024_PP.create();
124 a = new double[size];
125 for (int i = 0; i < size; i++) {
126 long bits = rng.nextLong() & SIGN_MATISSA_MASK;
127
128 if (rng.nextDouble() < edge) {
129 bits |= EXP_SMALL;
130 } else {
131 bits |= EXP_BIG;
132 }
133 a[i] = Double.longBitsToDouble(bits);
134 }
135 }
136 }
137
138
139
140
141 @State(Scope.Benchmark)
142 public static class BiFactors {
143
144 private static final long EXP_SMALL = Double.doubleToRawLongBits(1.0);
145
146
147
148
149 @Param({"5000"})
150 private int size;
151
152
153
154
155
156
157
158
159
160 @Param({"600", "1000", "1023"})
161 private int exp;
162
163
164
165
166
167
168
169
170
171 @Param({"1", "0.95", "0.9"})
172 private double edge;
173
174
175 private double[] a;
176
177
178
179
180
181
182 public double[] getFactors() {
183 return a;
184 }
185
186
187
188
189 @Setup
190 public void setup() {
191
192 final double d = Math.scalb(1.0, exp);
193 assert Double.isInfinite(d * d) : "Product of big numbers does not overflow";
194 final long expBig = Double.doubleToRawLongBits(d);
195
196 final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_1024_PP.create();
197 a = new double[size * 2];
198 for (int i = 0; i < a.length; i++) {
199 long bits = rng.nextLong() & SIGN_MATISSA_MASK;
200
201 if (rng.nextDouble() < edge) {
202 bits |= EXP_SMALL;
203 } else {
204 bits |= expBig;
205 }
206 a[i] = Double.longBitsToDouble(bits);
207 }
208 }
209 }
210
211
212
213
214 @State(Scope.Benchmark)
215 public static class NonNormalNumbers {
216
217 private static final double[] NON_NORMAL =
218 {Double.POSITIVE_INFINITY, Double.NaN, Double.MIN_NORMAL};
219
220
221
222
223 @Param({"10000"})
224 private int size;
225
226
227
228
229 @Param({"1", "0.999", "0.99", "0.9"})
230 private double edge;
231
232
233 private double[] a;
234
235
236
237
238
239
240 public double[] getFactors() {
241 return a;
242 }
243
244
245
246
247 @Setup
248 public void setup() {
249 final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_1024_PP.create();
250 a = new double[size];
251 for (int i = 0; i < size; i++) {
252
253 double value = rng.nextDouble() * (rng.nextBoolean() ? -1 : 1);
254
255 if (rng.nextDouble() < edge) {
256 value *= NON_NORMAL[rng.nextInt(NON_NORMAL.length)];
257 }
258 a[i] = value;
259 }
260 }
261 }
262
263
264
265
266 @State(Scope.Benchmark)
267 public static class SplitMethod {
268
269
270
271 @Param({NONE, "dekker", "dekkerAbs", "dekkerRaw", "bits"})
272 private String name;
273
274
275 private DoubleUnaryOperator fun;
276
277
278
279
280
281
282 public DoubleUnaryOperator getFunction() {
283 return fun;
284 }
285
286
287
288
289 @Setup
290 public void setup() {
291 if (NONE.equals(name)) {
292 fun = a -> a;
293 } else if ("dekker".equals(name)) {
294 fun = DoubleSplitPerformance::splitDekker;
295 } else if ("dekkerAbs".equals(name)) {
296 fun = DoubleSplitPerformance::splitDekkerAbs;
297 } else if ("dekkerRaw".equals(name)) {
298 fun = DoubleSplitPerformance::splitDekkerRaw;
299 } else if ("bits".equals(name)) {
300 fun = DoubleSplitPerformance::splitBits;
301 } else {
302 throw new IllegalStateException("Unknown split method: " + name);
303 }
304 }
305 }
306
307
308
309
310 @State(Scope.Benchmark)
311 public static class NonNormalMethod {
312
313
314
315 @Param({NONE, "if", "exponent", "exponent2"})
316 private String name;
317
318
319 private DoublePredicate fun;
320
321
322
323
324
325
326 public DoublePredicate getFunction() {
327 return fun;
328 }
329
330
331
332
333 @Setup
334 public void setup() {
335 if (NONE.equals(name)) {
336 fun = a -> false;
337 } else if ("if".equals(name)) {
338 fun = DoubleSplitPerformance::isNotNormalIf;
339 } else if ("exponent".equals(name)) {
340 fun = DoubleSplitPerformance::isNotNormalExponent;
341 } else if ("exponent2".equals(name)) {
342 fun = DoubleSplitPerformance::isNotNormalExponent2;
343 } else {
344 throw new IllegalStateException("Unknown is non-normal method: " + name);
345 }
346 }
347 }
348
349
350
351
352 @State(Scope.Benchmark)
353 public static class RoundoffMethod {
354
355
356
357 @Param({NONE, "multiply", "multiplyUnscaled",
358 "productLow", "productLow1", "productLow2", "productLow3", "productLowSplit",
359 "productLowUnscaled"})
360 private String name;
361
362
363 private DoubleBinaryOperator fun;
364
365
366
367
368
369
370 public DoubleBinaryOperator getFunction() {
371 return fun;
372 }
373
374
375
376
377 @Setup
378 public void setup() {
379 if (NONE.equals(name)) {
380
381
382 fun = (x, y) -> x * y;
383 } else if ("multiply".equals(name)) {
384 final DoublePrecision.Quad result = new DoublePrecision.Quad();
385 fun = (x, y) -> {
386 DoublePrecision.multiply(x, y, result);
387 return result.lo;
388 };
389 } else if ("productLow".equals(name)) {
390 fun = (x, y) -> DoublePrecision.productLow(x, y, x * y);
391 } else if ("productLow1".equals(name)) {
392 fun = (x, y) -> DoublePrecision.productLow1(x, y, x * y);
393 } else if ("productLow2".equals(name)) {
394 fun = (x, y) -> DoublePrecision.productLow2(x, y, x * y);
395 } else if ("productLow3".equals(name)) {
396 fun = (x, y) -> DoublePrecision.productLow3(x, y, x * y);
397 } else if ("productLowSplit".equals(name)) {
398 fun = (x, y) -> DoublePrecision.productLowSplit(x, y, x * y);
399 } else if ("multiplyUnscaled".equals(name)) {
400 final DoublePrecision.Quad result = new DoublePrecision.Quad();
401 fun = (x, y) -> {
402 DoublePrecision.multiplyUnscaled(x, y, result);
403 return result.lo;
404 };
405 } else if ("productLowUnscaled".equals(name)) {
406 fun = (x, y) -> DoublePrecision.productLowUnscaled(x, y, x * y);
407 } else {
408 throw new IllegalStateException("Unknown round-off method: " + name);
409 }
410 }
411 }
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434 private static double splitDekker(double value) {
435
436 if (value >= SAFE_UPPER || value <= -SAFE_UPPER) {
437
438 final double x = value * DOWN_SCALE;
439 final double c = MULTIPLIER * x;
440 final double hi = (c - (c - x)) * UP_SCALE;
441 if (Double.isInfinite(hi)) {
442
443
444
445
446
447
448
449
450
451
452
453 return Double.longBitsToDouble(Double.doubleToRawLongBits(value) & ZERO_LOWER_27_BITS);
454 }
455 return hi;
456 }
457
458 final double c = MULTIPLIER * value;
459 return c - (c - value);
460 }
461
462
463
464
465
466
467
468
469
470 private static double splitDekkerAbs(double value) {
471 if (Math.abs(value) >= SAFE_UPPER) {
472 final double x = value * DOWN_SCALE;
473 final double c = MULTIPLIER * x;
474 final double hi = (c - (c - x)) * UP_SCALE;
475 if (Double.isInfinite(hi)) {
476 return Double.longBitsToDouble(Double.doubleToRawLongBits(value) & ZERO_LOWER_27_BITS);
477 }
478 return hi;
479 }
480 final double c = MULTIPLIER * value;
481 return c - (c - value);
482 }
483
484
485
486
487
488
489
490
491 private static double splitDekkerRaw(double value) {
492 final double c = MULTIPLIER * value;
493 return c - (c - value);
494 }
495
496
497
498
499
500
501
502 private static double splitBits(double value) {
503 return Double.longBitsToDouble(Double.doubleToRawLongBits(value) & ZERO_LOWER_27_BITS);
504 }
505
506
507
508
509
510
511
512
513
514 private static boolean isNotNormalIf(double a) {
515 final double abs = Math.abs(a);
516 return abs <= Double.MIN_NORMAL || !(abs <= Double.MAX_VALUE);
517 }
518
519
520
521
522
523
524
525 private static boolean isNotNormalExponent(double a) {
526
527
528
529
530 final int baisedExponent = ((int) (Double.doubleToRawLongBits(a) >>> 52)) & 0x7ff;
531 return ((baisedExponent - 1) & 0xffff) >= 2046;
532 }
533
534
535
536
537
538
539
540 private static boolean isNotNormalExponent2(double a) {
541
542
543
544
545 final int baisedExponent = ((int) (Double.doubleToRawLongBits(a) >>> 52)) & 0x7ff;
546
547 return baisedExponent + Integer.MIN_VALUE - 1 >= 2046 + Integer.MIN_VALUE;
548 }
549
550
551
552
553
554
555
556
557
558
559 @Benchmark
560 public void high(Numbers numbers, Blackhole bh, SplitMethod method) {
561 final DoubleUnaryOperator fun = method.getFunction();
562 final double[] a = numbers.getNumbers();
563 for (int i = 0; i < a.length; i++) {
564 bh.consume(fun.applyAsDouble(a[i]));
565 }
566 }
567
568
569
570
571
572
573
574
575 @Benchmark
576 public void low(Numbers numbers, Blackhole bh, SplitMethod method) {
577 final DoubleUnaryOperator fun = method.getFunction();
578 final double[] a = numbers.getNumbers();
579 for (int i = 0; i < a.length; i++) {
580 bh.consume(a[i] - fun.applyAsDouble(a[i]));
581 }
582 }
583
584
585
586
587
588
589
590
591 @Benchmark
592 public void nonNormal(NonNormalNumbers numbers, Blackhole bh, NonNormalMethod method) {
593 final DoublePredicate fun = method.getFunction();
594 final double[] a = numbers.getFactors();
595 for (int i = 0; i < a.length; i++) {
596 bh.consume(fun.test(a[i]));
597 }
598 }
599
600
601
602
603
604
605
606
607 @Benchmark
608 public void productLow(BiFactors factors, Blackhole bh, RoundoffMethod method) {
609 final DoubleBinaryOperator fun = method.getFunction();
610 final double[] a = factors.getFactors();
611 for (int i = 0; i < a.length; i += 2) {
612 bh.consume(fun.applyAsDouble(a[i], a[i + 1]));
613 }
614 }
615 }