View Javadoc
1   package org.djutils.complex.demo;
2   
3   import org.djutils.complex.Complex;
4   
5   /**
6    * PerformanceTests.java. <br>
7    * <br>
8    * Copyright (c) 2020-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
9    * for project information <a href="https://djutils.org" target="_blank"> https://djutils.org</a>. The DJUTILS project is
10   * distributed under a three-clause BSD-style license, which can be found at
11   * <a href="https://djutils.org/docs/license.html" target="_blank"> https://djutils.org/docs/license.html</a>. <br>
12   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
13   * @author <a href="https://www.tudelft.nl/pknoppers">Peter Knoppers</a>
14   */
15  public final class PerformanceTests
16  {
17      /**
18       * Do not instantiate.
19       */
20      private PerformanceTests()
21      {
22          // Do not instantiate
23      }
24  
25      /**
26       * Measure performance of atan2, hypot, sine and cosine.
27       * @param args String[]; the command line arguments (not used)
28       */
29      public static void main(final String[] args)
30      {
31          // Ensure that all classes are loaded before measuring things
32          Math.atan2(0.5, 1.5);
33          Math.hypot(1.2, 3.4);
34          Complex.hypot(1.2, 3.4);
35          Math.sin(0.8);
36          Math.cos(0.6);
37          Math.sqrt(2.3);
38  
39          int iterations = 100000000;
40          long startNanos = System.nanoTime();
41          for (int i = 0; i < iterations; i++)
42          {
43              @SuppressWarnings("unused")
44              double x = 0.1 + i / 1000.0;
45              @SuppressWarnings("unused")
46              double y = -0.5 + i / 2000.0;
47          }
48          long nowNanos = System.nanoTime();
49          long baseNanos = nowNanos - startNanos;
50          System.out.println(String.format("              base loop: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
51                  baseNanos / 1000000000.0, 1.0 * baseNanos / iterations));
52  
53          startNanos = System.nanoTime();
54          for (int i = 0; i < iterations; i++)
55          {
56              double x = 0.1 + i / 1000.0;
57              double y = -0.5 + i / 2000.0;
58              Math.atan2(y, x);
59          }
60          nowNanos = System.nanoTime();
61          long durationNanos = nowNanos - startNanos - baseNanos;
62          System.out.println(String.format("             Math.atan2: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
63                  durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
64  
65          startNanos = System.nanoTime();
66          for (int i = 0; i < iterations; i++)
67          {
68              double x = 0.1 + i / 1000.0;
69              double y = -0.5 + i / 2000.0;
70              Math.hypot(y, x);
71          }
72          nowNanos = System.nanoTime();
73          durationNanos = nowNanos - startNanos - baseNanos;
74          System.out.println(String.format("             Math.hypot: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
75                  durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
76          
77          startNanos = System.nanoTime();
78          for (int i = 0; i < iterations; i++)
79          {
80              double x = 0.1 + i / 1000.0;
81              double y = -0.5 + i / 2000.0;
82              PerformanceTests.hypotA(y, x);
83          }
84          nowNanos = System.nanoTime();
85          durationNanos = nowNanos - startNanos - baseNanos;
86          System.out.println(String.format("PerformanceTests.hypotA: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
87                  durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
88          
89          startNanos = System.nanoTime();
90          for (int i = 0; i < iterations; i++)
91          {
92              double x = 0.1 + i / 1000.0;
93              double y = -0.5 + i / 2000.0;
94              PerformanceTests.hypotB(y, x);
95          }
96          nowNanos = System.nanoTime();
97          durationNanos = nowNanos - startNanos - baseNanos;
98          System.out.println(String.format("PerformanceTests.hypotB: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
99                  durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
100         
101         startNanos = System.nanoTime();
102         for (int i = 0; i < iterations; i++)
103         {
104             double x = 0.1 + i / 1000.0;
105             double y = -0.5 + i / 2000.0;
106             PerformanceTests.hypotC(y, x);
107         }
108         nowNanos = System.nanoTime();
109         durationNanos = nowNanos - startNanos - baseNanos;
110         System.out.println(String.format("PerformanceTests.hypotC: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
111                 durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
112        
113         startNanos = System.nanoTime();
114         for (int i = 0; i < iterations; i++)
115         {
116             double x = 0.1 + i / 1000.0;
117             double y = -0.5 + i / 2000.0;
118             Complex.hypot(y, x);
119         }
120         nowNanos = System.nanoTime();
121         durationNanos = nowNanos - startNanos - baseNanos;
122         System.out.println(String.format("          Complex.hypot: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
123                 durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
124   
125         startNanos = System.nanoTime();
126         for (int i = 0; i < iterations; i++)
127         {
128             double x = 0.1 + i / 1000000.0;
129             Math.sin(x);
130         }
131         nowNanos = System.nanoTime();
132         durationNanos = nowNanos - startNanos - baseNanos;
133         System.out.println(String.format("               Math.sin: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
134                 durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
135    
136         startNanos = System.nanoTime();
137         for (int i = 0; i < iterations; i++)
138         {
139             double x = 0.1 + i / 1000000.0;
140             Math.cos(x);
141         }
142         nowNanos = System.nanoTime();
143         durationNanos = nowNanos - startNanos - baseNanos;
144         System.out.println(String.format("               Math.cos: %d invocations in %.6f s (%.1f ns/invocation)", iterations,
145                 durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));  
146    
147         startNanos = System.nanoTime();
148         for (int i = 0; i < iterations; i++)
149         {
150             double x = 0.1 + i / 1000.0;
151             double y = -0.5 + i / 2000.0;
152             Math.sqrt(x * x + y * y);
153         }
154         nowNanos = System.nanoTime();
155         durationNanos = nowNanos - startNanos - baseNanos;
156         System.out.println(String.format("     Math.sqrt(x*x+y*y): %d invocations in %.6f s (%.1f ns/invocation)",
157                 iterations, durationNanos / 1000000000.0, 1.0 * durationNanos / iterations));
158     }
159 
160     /** 2^450. */
161     private static final double TWO_POW_450 = Double.longBitsToDouble(0x5C10000000000000L);
162 
163     /** 2^-450. */
164     private static final double TWO_POW_N450 = Double.longBitsToDouble(0x23D0000000000000L);
165 
166     /** 2^750? */
167     private static final double TWO_POW_750 = Double.longBitsToDouble(0x6ED0000000000000L);
168 
169     /** 2^-750? */
170     private static final double TWO_POW_N750 = Double.longBitsToDouble(0x1110000000000000L);
171 
172     /**
173      * Implementation of hypot taken from https://stackoverflow.com/questions/3764978/why-hypot-function-is-so-slow .
174      * @param px double; x
175      * @param py double; y
176      * @return double; the hypot of x, and y
177      */
178     public static double hypotA(final double px, final double py)
179     {
180         double x = Math.abs(px);
181         double y = Math.abs(py);
182         if (y < x)
183         {
184             double a = x;
185             x = y;
186             y = a;
187         }
188         else if (!(y >= x))
189         { // Testing if we have some NaN.
190             if ((x == Double.POSITIVE_INFINITY) || (y == Double.POSITIVE_INFINITY))
191             {
192                 return Double.POSITIVE_INFINITY;
193             }
194             else
195             {
196                 return Double.NaN;
197             }
198         }
199         if (y - x == y)
200         { // x too small to substract from y
201             return y;
202         }
203         else
204         {
205             double factor;
206             if (x > TWO_POW_450)
207             { // 2^450 < x < y
208                 x *= TWO_POW_N750;
209                 y *= TWO_POW_N750;
210                 factor = TWO_POW_750;
211             }
212             else if (y < TWO_POW_N450)
213             { // x < y < 2^-450
214                 x *= TWO_POW_750;
215                 y *= TWO_POW_750;
216                 factor = TWO_POW_N750;
217             }
218             else
219             {
220                 factor = 1.0;
221             }
222             return factor * Math.sqrt(x * x + y * y);
223         }
224     }
225 
226     /**
227      * <b>hypot</b>.
228      * @param px double; x
229      * @param py double; y
230      * @return sqrt(x*x +y*y) without intermediate overflow or underflow.
231      * Note {@link Math#hypot} is unnecessarily slow. This returns the identical result to Math.hypot with reasonable run times
232      *       (~40 nsec vs. 800 nsec).
233      *       The logic for computing z is copied from "Freely Distributable Math Library" fdlibm's e_hypot.c. This minimizes
234      *       rounding error to provide 1 ulb accuracy.
235      */
236     public static double hypotB(final double px, final double py)
237     {
238         if (Double.isInfinite(px) || Double.isInfinite(py))
239         {
240             return Double.POSITIVE_INFINITY;
241         }
242         if (Double.isNaN(px) || Double.isNaN(py))
243         {
244             return Double.NaN;
245         }
246 
247         double x = Math.abs(px);
248         double y = Math.abs(py);
249 
250         if (x < y)
251         {
252             double d = x;
253             x = y;
254             y = d;
255         }
256 
257         int xi = Math.getExponent(x);
258         int yi = Math.getExponent(y);
259 
260         if (xi > yi + 27)
261         {
262             return x;
263         }
264 
265         int bias = 0;
266         if (xi > 510 || xi < -511)
267         {
268             bias = xi;
269             x = Math.scalb(x, -bias);
270             y = Math.scalb(y, -bias);
271         }
272 
273         // translated from "Freely Distributable Math Library" e_hypot.c to minimize rounding errors
274         double z = 0;
275         if (x > 2 * y)
276         {
277             double x1 = Double.longBitsToDouble(Double.doubleToLongBits(x) & 0xffffffff00000000L);
278             double x2 = x - x1;
279             z = Math.sqrt(x1 * x1 + (y * y + x2 * (x + x1)));
280         }
281         else
282         {
283             double t = 2 * x;
284             double t1 = Double.longBitsToDouble(Double.doubleToLongBits(t) & 0xffffffff00000000L);
285             double t2 = t - t1;
286             double y1 = Double.longBitsToDouble(Double.doubleToLongBits(y) & 0xffffffff00000000L);
287             double y2 = y - y1;
288             double xMinusY = x - y;
289             z = Math.sqrt(t1 * y1 + (xMinusY * xMinusY + (t1 * y2 + t2 * y))); // Note: 2*x*y <= x*x + y*y
290         }
291 
292         if (bias == 0)
293         {
294             return z;
295         }
296         else
297         {
298             return Math.scalb(z, bias);
299         }
300     }
301     
302     /** Precision limit. */
303     private static final double EPSILONSQRT = Math.sqrt(Math.ulp(1.0) / 2);
304     /** Square root of smallest floating point value. */
305     private static final double SQRT_OF_MIN_VALUE = Math.sqrt(Double.MIN_VALUE);
306     /** Square root of biggest floating point value. */
307     private static final double SQRT_OF_MAX_VALUE = Math.sqrt(Double.MAX_VALUE);
308     
309     /**
310      * Better implementation of the hypotenuse function (faster and more accurate than the one in the java Math library). <br>
311      * Derived from <a href="https://arxiv.org/abs/1904.09481">An improved algorithm for hypot(a, b) by Carlos F. Borges</a>.
312      * @param x double; the x value
313      * @param y double; the y value
314      * @return double; hypot(x, y)
315      */
316     public static double hypotC(final double x, final double y)
317     {
318         if (x != x || y != y)
319         {
320             return Double.NaN;
321         }
322         double absX = Math.abs(x);
323         double absY = Math.abs(y);
324         if (absX == Double.POSITIVE_INFINITY || absY == Double.POSITIVE_INFINITY)
325         {
326             return Double.POSITIVE_INFINITY;
327         }
328         if (absX < absY)
329         {
330             double swap = absX;
331             absX = absY;
332             absY = swap;
333         }
334         if (absY <= absX * EPSILONSQRT)
335         {
336             return absX;
337         }
338         double scale = SQRT_OF_MIN_VALUE;
339         if (absX > SQRT_OF_MAX_VALUE)
340         {
341             scale = SQRT_OF_MIN_VALUE;
342             absX *= scale;
343             absY *= scale;
344             scale = 1.0 / scale;
345         }
346         else if (absY < SQRT_OF_MIN_VALUE)
347         {
348             scale = SQRT_OF_MIN_VALUE;
349             absX /= scale;
350             absY /= scale;
351         }
352         else
353         {
354             scale = 1.0;
355         }
356         double h = Math.sqrt(Math.fma(absX, absX, absY * absY));
357         double hsq = h * h;
358         double xsq = absX * absX;
359         double a = Math.fma(-absY, absY, hsq - xsq) + Math.fma(h, h, -hsq) - Math.fma(absX, absX, -xsq);
360         return scale * (h - a / (2.0 * h));
361     }
362 
363 }