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  package org.apache.commons.numbers.examples.jmh.core;
18  
19  import java.util.concurrent.TimeUnit;
20  import java.util.function.ToDoubleBiFunction;
21  import java.util.function.ToDoubleFunction;
22  
23  import org.apache.commons.numbers.core.Sum;
24  import org.apache.commons.rng.UniformRandomProvider;
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  
39  /**
40   * Executes a benchmark to measure the speed of operations in the {@link Sum} class.
41   */
42  @BenchmarkMode(Mode.AverageTime)
43  @OutputTimeUnit(TimeUnit.NANOSECONDS)
44  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
45  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
46  @State(Scope.Benchmark)
47  @Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
48  public class SumPerformance {
49      /**
50       * The seed to use to create the random benchmark input.
51       * Using a fixed seed ensures the same values are created across benchmarks.
52       */
53      private static final long SEED = System.currentTimeMillis();
54  
55      /** Class providing double arrays for benchmarks.
56       */
57      @State(Scope.Benchmark)
58      public static class ArrayInput {
59  
60          /** Number of array samples. */
61          @Param("100000")
62          private int samples;
63  
64          /** Number of values in each input array. */
65          @Param("50")
66          private int len;
67  
68          /** Minimum possible double exponent. */
69          @Param("-550")
70          private int minExp;
71  
72          /** Maximum possible double exponent. */
73          @Param("+550")
74          private int maxExp;
75  
76          /** Range of exponents within a single array. */
77          @Param("26")
78          private int expRange;
79  
80          /** First set of input arrays. */
81          private double[][] a;
82  
83          /** Second set of input arrays. */
84          private double[][] b;
85  
86          /** Get the first set of input arrays.
87           * @return first set of input arrays
88           */
89          public double[][] getA() {
90              return a;
91          }
92  
93          /** Get the second set of input arrays.
94           * @return second set of input arrays
95           */
96          public double[][] getB() {
97              return b;
98          }
99  
100         /** Create the input arrays for the instance. */
101         @Setup
102         public void createArrays() {
103             final UniformRandomProvider rng = RandomSource.XO_RO_SHI_RO_1024_PP.create(SEED);
104 
105             a = new double[samples][];
106             b = new double[samples][];
107             for (int i = 0; i < samples; ++i) {
108                 // pick a general range for the array element exponents and then
109                 // create values within that range
110                 final int vMidExp = rng.nextInt(maxExp - minExp + 1) + minExp;
111                 final int vExpRadius = expRange / 2;
112                 final int vMinExp = vMidExp - vExpRadius;
113                 final int vMaxExp = vMidExp + vExpRadius;
114 
115                 a[i] = DoubleUtils.randomArray(len, vMinExp, vMaxExp, rng);
116                 b[i] = DoubleUtils.randomArray(len, vMinExp, vMaxExp, rng);
117             }
118         }
119     }
120 
121     /** Run a benchmark for a function that accepts a single array and produces a double result.
122      * @param input benchmark input
123      * @param bh data sink
124      * @param fn function to benchmark
125      */
126     private static void runSingle(final ArrayInput input, final Blackhole bh,
127             final ToDoubleFunction<double[]> fn) {
128         final double[][] a = input.getA();
129         for (int i = 0; i < a.length; ++i) {
130             bh.consume(fn.applyAsDouble(a[i]));
131         }
132     }
133 
134     /** Run a benchmark for a function that accepts a two arrays and produces a double result.
135      * @param input benchmark input
136      * @param bh data sink
137      * @param fn function to benchmark
138      */
139     private static void runDouble(final ArrayInput input, final Blackhole bh,
140             final ToDoubleBiFunction<double[], double[]> fn) {
141         final double[][] a = input.getA();
142         final double[][] b = input.getB();
143         for (int i = 0; i < a.length; ++i) {
144             bh.consume(fn.applyAsDouble(a[i], b[i]));
145         }
146     }
147 
148     /** Benchmark baseline for functions that use a single input array.
149      * @param input benchmark input
150      * @param bh data sink
151      */
152     @Benchmark
153     public void baselineSingle(final ArrayInput input, final Blackhole bh) {
154         runSingle(input, bh, a -> 0d);
155     }
156 
157     /** Benchmark baseline for functions that use two input arrays.
158      * @param input benchmark input
159      * @param bh data sink
160      */
161     @Benchmark
162     public void baselineDouble(final ArrayInput input, final Blackhole bh) {
163         runDouble(input, bh, (a, b) -> 0d);
164     }
165 
166     /** Benchmark testing {@link Sum} addition performance.
167      * @param input benchmark input
168      * @param bh data sink
169      */
170     @Benchmark
171     public void sum(final ArrayInput input, final Blackhole bh) {
172         runSingle(input, bh, a -> Sum.of(a).getAsDouble());
173     }
174 
175     /** Benchmark testing {@link Sum} linear combination performance.
176      * @param input benchmark input
177      * @param bh data sink
178      */
179     @Benchmark
180     public void sumOfProducts(final ArrayInput input, final Blackhole bh) {
181         runDouble(input, bh, (a, b) -> Sum.ofProducts(a, b).getAsDouble());
182     }
183 }