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.numbers.examples.jmh.complex;
19  
20  import org.apache.commons.math3.util.FastMath;
21  import org.apache.commons.numbers.core.Precision;
22  import org.openjdk.jmh.annotations.Benchmark;
23  import org.openjdk.jmh.annotations.BenchmarkMode;
24  import org.openjdk.jmh.annotations.Fork;
25  import org.openjdk.jmh.annotations.Measurement;
26  import org.openjdk.jmh.annotations.Mode;
27  import org.openjdk.jmh.annotations.OutputTimeUnit;
28  import org.openjdk.jmh.annotations.Param;
29  import org.openjdk.jmh.annotations.Scope;
30  import org.openjdk.jmh.annotations.Setup;
31  import org.openjdk.jmh.annotations.State;
32  import org.openjdk.jmh.annotations.Warmup;
33  import org.openjdk.jmh.infra.Blackhole;
34  import java.util.SplittableRandom;
35  import java.util.concurrent.TimeUnit;
36  import java.util.function.DoubleSupplier;
37  import java.util.function.DoubleUnaryOperator;
38  import java.util.function.Supplier;
39  import java.util.stream.DoubleStream;
40  
41  /**
42   * Executes a benchmark to estimate the speed of sin/cos operations.
43   * This compares the Math implementation to FastMath. It would be possible
44   * to adapt FastMath to compute sin/cos together as they both use a common
45   * initial stage to map the value to the domain [0, pi/2).
46   */
47  @BenchmarkMode(Mode.AverageTime)
48  @OutputTimeUnit(TimeUnit.NANOSECONDS)
49  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
50  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
51  @State(Scope.Benchmark)
52  @Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
53  public class SinCosPerformance {
54      /**
55       * An array of edge numbers that will produce edge case results from sin/cos functions:
56       * {@code +/-inf, +/-0, nan}.
57       */
58      private static final double[] EDGE_NUMBERS = {
59          Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, 0.0, -0.0, Double.NaN};
60  
61      /**
62       * Contains the size of numbers.
63       */
64      @State(Scope.Benchmark)
65      public static class NumberSize {
66          /**
67           * The size of the data.
68           */
69          @Param({"1000"})
70          private int size;
71  
72          /**
73           * Gets the size.
74           *
75           * @return the size
76           */
77          public int getSize() {
78              return size;
79          }
80      }
81  
82      /**
83       * Contains an array of numbers.
84       */
85      public abstract static class BaseNumbers extends NumberSize {
86          /** The numbers. */
87          protected double[] numbers;
88  
89          /**
90           * Gets the numbers.
91           *
92           * @return the numbers
93           */
94          public double[] getNumbers() {
95              return numbers;
96          }
97  
98          /**
99           * Create the complex numbers.
100          */
101         @Setup
102         public void setup() {
103             numbers = createNumbers(new SplittableRandom());
104             // Verify functions
105             for (final double x : numbers) {
106                 final double sin = Math.sin(x);
107                 assertEquals(sin, FastMath.sin(x), 1, () -> "sin " + x);
108                 final double cos = Math.cos(x);
109                 assertEquals(cos, FastMath.cos(x), 1, () -> "cos " + x);
110             }
111         }
112 
113         /**
114          * Creates the numbers.
115          *
116          * @param rng Random number generator.
117          * @return the random number
118          */
119         protected abstract double[] createNumbers(SplittableRandom rng);
120     }
121 
122     /**
123      * Contains an array of numbers.
124      */
125     @State(Scope.Benchmark)
126     public static class Numbers extends BaseNumbers {
127         /**
128          * The type of the data.
129          */
130         @Param({"pi", "pi/2", "random", "edge"})
131         private String type;
132 
133         /** {@inheritDoc} */
134         @Override
135         protected double[] createNumbers(SplittableRandom rng) {
136             DoubleSupplier generator;
137             if ("pi".equals(type)) {
138                 generator = () -> rng.nextDouble() * 2 * Math.PI - Math.PI;
139             } else if ("pi/2".equals(type)) {
140                 generator = () -> rng.nextDouble() * Math.PI - Math.PI / 2;
141             } else if ("random".equals(type)) {
142                 generator = () -> createRandomNumber(rng);
143             } else if ("edge".equals(type)) {
144                 generator = () -> createEdgeNumber(rng);
145             } else {
146                 throw new IllegalStateException("Unknown number type: " + type);
147             }
148             return DoubleStream.generate(generator).limit(getSize()).toArray();
149         }
150     }
151 
152     /**
153      * Contains an array of uniform numbers.
154      */
155     @State(Scope.Benchmark)
156     public static class UniformNumbers extends BaseNumbers {
157         /**
158          * The range of the data.
159          *
160          * <p>Note: Representations of half-pi and pi are rounded down
161          * to ensure the value is less than the exact representation.
162          */
163         @Param({"1.57079", "3.14159", "10", "100", "1e4", "1e8", "1e16", "1e32"})
164         private double range;
165 
166         /** {@inheritDoc} */
167         @Override
168         protected double[] createNumbers(SplittableRandom rng) {
169             return rng.doubles(getSize(), -range, range).toArray();
170         }
171     }
172 
173     /**
174      * Assert the values are equal to the given ulps, else throw an AssertionError.
175      *
176      * @param x the x
177      * @param y the y
178      * @param maxUlps the max ulps for equality
179      * @param msg the message upon failure
180      */
181     static void assertEquals(double x, double y, int maxUlps, Supplier<String> msg) {
182         if (!Precision.equalsIncludingNaN(x, y, maxUlps)) {
183             throw new AssertionError(msg.get() + ": " + x + " != " + y);
184         }
185     }
186 
187     /**
188      * Creates a random double number uniformly distributed over a range much larger than [-pi, pi].
189      * The data is a test of the reduction operation to convert a large number to the domain
190      * [0, pi/2).
191      *
192      * @param rng Random number generator.
193      * @return the random number
194      */
195     private static double createRandomNumber(SplittableRandom rng) {
196         return rng.nextDouble(-1e200, 1e200);
197     }
198 
199     /**
200      * Creates a random double number that will be an edge case:
201      * {@code +/-inf, +/-0, nan}.
202      *
203      * @param rng Random number generator.
204      * @return the random number
205      */
206     private static double createEdgeNumber(SplittableRandom rng) {
207         return EDGE_NUMBERS[rng.nextInt(EDGE_NUMBERS.length)];
208     }
209 
210     /**
211      * Apply the function to all the numbers.
212      *
213      * @param numbers Numbers.
214      * @param fun Function.
215      * @param bh Data sink.
216      */
217     private static void apply(double[] numbers, DoubleUnaryOperator fun, Blackhole bh) {
218         for (int i = 0; i < numbers.length; i++) {
219             bh.consume(fun.applyAsDouble(numbers[i]));
220         }
221     }
222 
223     /**
224      * Identity function. This can be used to measure overhead of copy array creation.
225      *
226      * @param z Complex number.
227      * @return the number
228      */
229     private static double identity(double z) {
230         return z;
231     }
232 
233     // Benchmark methods.
234     //
235     // Benchmarks use function references to perform different operations on the numbers.
236     // Tests show that explicit programming of the same benchmarks run in the same time.
237     // For reference examples are provided for sin(x).
238 
239     /**
240      * Explicit benchmark without using a method reference.
241      * This is commented out as it exists for reference purposes.
242      *
243      * @param numbers Numbers.
244      * @param bh Data sink.
245      */
246     //@Benchmark
247     public void mathSin2(Numbers numbers, Blackhole bh) {
248         final double[] x = numbers.getNumbers();
249         for (int i = 0; i < x.length; i++) {
250             bh.consume(Math.sin(x[i]));
251         }
252     }
253 
254     /**
255      * Baseline the JMH overhead for all the benchmarks that create numbers. All other
256      * methods are expected to be slower than this.
257      *
258      * @param numbers Numbers.
259      * @param bh Data sink.
260      */
261     @Benchmark
262     public void baselineIdentity(Numbers numbers, Blackhole bh) {
263         apply(numbers.getNumbers(), SinCosPerformance::identity, bh);
264     }
265 
266     /**
267      * Benchmark {@link Math#sin(double)}.
268      *
269      * @param numbers Numbers.
270      * @param bh Data sink.
271      */
272     @Benchmark
273     public void mathSin(Numbers numbers, Blackhole bh) {
274         apply(numbers.getNumbers(), Math::sin, bh);
275     }
276 
277     /**
278      * Benchmark {@link Math#cos(double)}.
279      *
280      * @param numbers Numbers.
281      * @param bh Data sink.
282      */
283     @Benchmark
284     public void mathCos(Numbers numbers, Blackhole bh) {
285         apply(numbers.getNumbers(), Math::cos, bh);
286     }
287 
288     /**
289      * Benchmark {@link FastMath#sin(double)}.
290      *
291      * @param numbers Numbers.
292      * @param bh Data sink.
293      */
294     @Benchmark
295     public void fastMathSin(Numbers numbers, Blackhole bh) {
296         apply(numbers.getNumbers(), FastMath::sin, bh);
297     }
298 
299     /**
300      * Benchmark {@link FastMath#cos(double)}.
301      *
302      * @param numbers Numbers.
303      * @param bh Data sink.
304      */
305     @Benchmark
306     public void fastMathCos(Numbers numbers, Blackhole bh) {
307         apply(numbers.getNumbers(), FastMath::cos, bh);
308     }
309 
310     /**
311      * Benchmark {@link Math#sin(double)} using a uniform range of numbers.
312      *
313      * @param numbers Numbers.
314      * @param bh Data sink.
315      */
316     @Benchmark
317     public void rangeMathSin(UniformNumbers numbers, Blackhole bh) {
318         apply(numbers.getNumbers(), Math::sin, bh);
319     }
320 
321     /**
322      * Benchmark {@link FastMath#sin(double)} using a uniform range of numbers.
323      *
324      * @param numbers Numbers.
325      * @param bh Data sink.
326      */
327     @Benchmark
328     public void rangeFastMathSin(UniformNumbers numbers, Blackhole bh) {
329         apply(numbers.getNumbers(), FastMath::sin, bh);
330     }
331 }