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.core;
19  
20  import java.math.MathContext;
21  import java.util.Arrays;
22  import java.util.concurrent.TimeUnit;
23  import java.util.function.IntFunction;
24  
25  import org.apache.commons.numbers.core.Sum;
26  import org.apache.commons.numbers.examples.jmh.core.LinearCombination.FourD;
27  import org.apache.commons.numbers.examples.jmh.core.LinearCombination.ND;
28  import org.apache.commons.numbers.examples.jmh.core.LinearCombination.ThreeD;
29  import org.apache.commons.numbers.examples.jmh.core.LinearCombination.TwoD;
30  import org.apache.commons.rng.UniformRandomProvider;
31  import org.apache.commons.rng.simple.RandomSource;
32  import org.openjdk.jmh.annotations.Benchmark;
33  import org.openjdk.jmh.annotations.BenchmarkMode;
34  import org.openjdk.jmh.annotations.Fork;
35  import org.openjdk.jmh.annotations.Measurement;
36  import org.openjdk.jmh.annotations.Mode;
37  import org.openjdk.jmh.annotations.OutputTimeUnit;
38  import org.openjdk.jmh.annotations.Param;
39  import org.openjdk.jmh.annotations.Scope;
40  import org.openjdk.jmh.annotations.Setup;
41  import org.openjdk.jmh.annotations.State;
42  import org.openjdk.jmh.annotations.Warmup;
43  import org.openjdk.jmh.infra.Blackhole;
44  
45  /**
46   * Executes a benchmark to measure the speed of operations in the {@link LinearCombination} class.
47   */
48  @BenchmarkMode(Mode.AverageTime)
49  @OutputTimeUnit(TimeUnit.NANOSECONDS)
50  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
51  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
52  @State(Scope.Benchmark)
53  @Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
54  public class LinearCombinationPerformance {
55      /**
56       * The seed to use to create the factors.
57       * Using a fixed seed ensures the same factors are created for the variable
58       * length arrays as for the small fixed size arrays.
59       */
60      private static final long SEED = System.currentTimeMillis();
61  
62      /**
63       * The factors to multiply.
64       */
65      @State(Scope.Benchmark)
66      public static class Factors {
67          /**
68           * The condition number of the generated data.
69           */
70          @Param({"1e20"})
71          private double c;
72  
73          /**
74           * The number of factors.
75           */
76          @Param({"1000"})
77          private int size;
78  
79          /** Factors a. */
80          private double[][] a;
81  
82          /** Factors b. */
83          private double[][] b;
84  
85          /**
86           * Gets the length of the array of factors.
87           * This exists to be overridden by factors of a specific length.
88           * The default is to create factors of length 4 for use in the inlined
89           * scalar product methods.
90           *
91           * @return the length
92           */
93          public int getLength() {
94              return 4;
95          }
96  
97          /**
98           * Gets the number of scalar products to compute.
99           *
100          * @return the size
101          */
102         public int getSize() {
103             return size;
104         }
105 
106         /**
107          * Gets the a factors.
108          *
109          * @param index the index
110          * @return Factors b.
111          */
112         public double[] getA(int index) {
113             return a[index];
114         }
115 
116         /**
117          * Gets the b factors.
118          *
119          * @param index the index
120          * @return Factors b.
121          */
122         public double[]  getB(int index) {
123             return b[index];
124         }
125 
126         /**
127          * Create the factors.
128          */
129         @Setup
130         public void setup() {
131             final UniformRandomProvider rng =
132                     RandomSource.XO_RO_SHI_RO_1024_PP.create(SEED);
133             // Use the ill conditioned data generation method.
134             // This requires an array of at least 6.
135             final int n = Math.max(6, getLength());
136             final double[] x = new double[n];
137             final double[] y = new double[n];
138             a = new double[size][];
139             b = new double[size][];
140             // Limit precision to allow large array lengths to be generated.
141             final MathContext mathContext = new MathContext(100);
142             for (int i = 0; i < size; i++) {
143                 LinearCombinationUtils.genDot(c, rng, x, y, null, mathContext);
144                 a[i] = Arrays.copyOf(x, getLength());
145                 b[i] = Arrays.copyOf(y, getLength());
146             }
147         }
148     }
149 
150     /**
151      * The factors to multiply of a specific length.
152      */
153     @State(Scope.Benchmark)
154     public static class LengthFactors extends Factors {
155         /**
156          * The length of each factors array.
157          */
158         @Param({"2", "3", "4", "8", "16", "32", "64"})
159         private int length;
160 
161         /** {@inheritDoc} */
162         @Override
163         public int getLength() {
164             return length;
165         }
166     }
167 
168     /**
169      * The {@link LinearCombination} implementation.
170      */
171     @State(Scope.Benchmark)
172     public static class Calculator {
173         /**
174          * The implementation name.
175          */
176         @Param({"standard",
177                 "current",
178                 "dekker",
179                 "dot2s",
180                 "dot2", "dot3", "dot4", "dot5", "dot6", "dot7",
181                 "exact",
182                 "extended", "extended2", "extended_exact", "extended_exact2",
183                 // Cached working double[] array.
184                 // Only faster when 'length' is >16. Below this the array
185                 // is small enough to be allocated locally
186                 // (Search for Thread Local Allocation Buffer (TLAB))
187                 "dot3c", "extendedc"})
188         private String name;
189 
190         /** The 2D implementation. */
191         private TwoD twod;
192         /** The 3D implementation. */
193         private ThreeD threed;
194         /** The 4D implementation. */
195         private FourD fourd;
196         /** The ND implementation. */
197         private ND nd;
198 
199         /**
200          * @return the 2D implementation
201          */
202         public TwoD getTwoD() {
203             return twod;
204         }
205 
206         /**
207          * @return the 3D implementation
208          */
209         public ThreeD getThreeD() {
210             return threed;
211         }
212 
213         /**
214          * @return the 4D implementation
215          */
216         public FourD getFourD() {
217             return fourd;
218         }
219 
220         /**
221          * @return the ND implementation
222          */
223         public ND getND() {
224             return nd;
225         }
226 
227         /**
228          * Setup the implementation.
229          */
230         @Setup
231         public void setup() {
232             if ("current".endsWith(name)) {
233                 twod = (a1, b1, a2, b2) ->
234                     Sum.create()
235                         .addProduct(a1, b1)
236                         .addProduct(a2, b2).getAsDouble();
237                 threed = (a1, b1, a2, b2, a3, b3) ->
238                     Sum.create()
239                         .addProduct(a1, b1)
240                         .addProduct(a2, b2)
241                         .addProduct(a3, b3).getAsDouble();
242                 fourd = (a1, b1, a2, b2, a3, b3, a4, b4) ->
243                     Sum.create()
244                         .addProduct(a1, b1)
245                         .addProduct(a2, b2)
246                         .addProduct(a3, b3)
247                         .addProduct(a4, b4).getAsDouble();
248                 nd = (a, b) -> Sum.ofProducts(a, b).getAsDouble();
249                 return;
250             }
251             // All implementations below are expected to implement all the interfaces.
252             if ("standard".endsWith(name)) {
253                 nd = LinearCombinations.StandardPrecision.INSTANCE;
254             } else if ("dekker".equals(name)) {
255                 nd = LinearCombinations.Dekker.INSTANCE;
256             } else if ("dot2s".equals(name)) {
257                 nd = LinearCombinations.Dot2s.INSTANCE;
258             } else if ("dot2".equals(name)) {
259                 nd = new LinearCombinations.DotK(2);
260             } else if ("dot3".equals(name)) {
261                 nd = LinearCombinations.DotK.DOT_3;
262             } else if ("dot4".equals(name)) {
263                 nd = LinearCombinations.DotK.DOT_4;
264             } else if ("dot5".equals(name)) {
265                 nd = LinearCombinations.DotK.DOT_5;
266             } else if ("dot6".equals(name)) {
267                 nd = LinearCombinations.DotK.DOT_6;
268             } else if ("dot7".equals(name)) {
269                 nd = LinearCombinations.DotK.DOT_7;
270             } else if ("exact".equals(name)) {
271                 nd = LinearCombinations.Exact.INSTANCE;
272             } else if ("extended".equals(name)) {
273                 nd = LinearCombinations.ExtendedPrecision.INSTANCE;
274             } else if ("extended2".equals(name)) {
275                 nd = LinearCombinations.ExtendedPrecision.DOUBLE;
276             } else if ("extended_exact".equals(name)) {
277                 nd = LinearCombinations.ExtendedPrecision.EXACT;
278             } else if ("extended_exact2".equals(name)) {
279                 nd = LinearCombinations.ExtendedPrecision.EXACT2;
280             } else if ("dot3c".equals(name)) {
281                 nd = new LinearCombinations.DotK(3, new CachedArrayFactory());
282             } else if ("extendedc".equals(name)) {
283                 nd = LinearCombinations.ExtendedPrecision.of(
284                         LinearCombinations.ExtendedPrecision.Summation.STANDARD, new CachedArrayFactory());
285             } else {
286                 throw new IllegalStateException("Unknown implementation: " + name);
287             }
288             // Possible class-cast exception for partial implementations...
289             twod = (TwoD) nd;
290             threed = (ThreeD) nd;
291             fourd = (FourD) nd;
292         }
293     }
294 
295     /**
296      * Create or return a cached array.
297      */
298     static final class CachedArrayFactory implements IntFunction<double[]> {
299         /** An empty double array. */
300         private static final double[] EMPTY = new double[0];
301 
302         /** The cached array. */
303         private double[] array = EMPTY;
304 
305         @Override
306         public double[] apply(int value) {
307             double[] a = array;
308             if (a.length < value) {
309                 array = a = new double[value];
310             }
311             return a;
312         }
313     }
314 
315     /**
316      * Compute the 2D scalar product for all the factors.
317      *
318      * @param factors Factors.
319      * @param bh Data sink.
320      * @param calc Scalar product calculator.
321      */
322     @Benchmark
323     public void twoD(Factors factors, Blackhole bh, Calculator calc) {
324         final TwoD fun = calc.getTwoD();
325         for (int i = 0; i < factors.getSize(); i++) {
326             final double[] a = factors.getA(i);
327             final double[] b = factors.getB(i);
328             bh.consume(fun.value(a[0], b[0], a[1], b[1]));
329         }
330     }
331 
332     /**
333      * Compute the 3D scalar product for all the factors.
334      *
335      * @param factors Factors.
336      * @param bh Data sink.
337      * @param calc Scalar product calculator.
338      */
339     @Benchmark
340     public void threeD(Factors factors, Blackhole bh, Calculator calc) {
341         final ThreeD fun = calc.getThreeD();
342         for (int i = 0; i < factors.getSize(); i++) {
343             final double[] a = factors.getA(i);
344             final double[] b = factors.getB(i);
345             bh.consume(fun.value(a[0], b[0], a[1], b[1], a[2], b[2]));
346         }
347     }
348 
349     /**
350      * Compute the 4D scalar product for all the factors.
351      *
352      * @param factors Factors.
353      * @param bh Data sink.
354      * @param calc Scalar product calculator.
355      */
356     @Benchmark
357     public void fourD(Factors factors, Blackhole bh, Calculator calc) {
358         final FourD fun = calc.getFourD();
359         for (int i = 0; i < factors.getSize(); i++) {
360             final double[] a = factors.getA(i);
361             final double[] b = factors.getB(i);
362             bh.consume(fun.value(a[0], b[0], a[1], b[1], a[2], b[2], a[3], b[3]));
363         }
364     }
365 
366     /**
367      * Compute the ND scalar product for all the factors.
368      *
369      * @param factors Factors.
370      * @param bh Data sink.
371      * @param calc Scalar product calculator.
372      */
373     @Benchmark
374     public void nD(LengthFactors factors, Blackhole bh, Calculator calc) {
375         final ND fun = calc.getND();
376         for (int i = 0; i < factors.getSize(); i++) {
377             // These should be pre-computed to the correct length
378             final double[] a = factors.getA(i);
379             final double[] b = factors.getB(i);
380             bh.consume(fun.value(a, b));
381         }
382     }
383 }