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.geometry.examples.jmh.euclidean;
18  
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.concurrent.TimeUnit;
22  import java.util.function.DoubleSupplier;
23  import java.util.function.Function;
24  import java.util.function.ToDoubleFunction;
25  import java.util.function.UnaryOperator;
26  
27  import org.apache.commons.geometry.core.Vector;
28  import org.apache.commons.geometry.euclidean.oned.Vector1D;
29  import org.apache.commons.geometry.euclidean.threed.Vector3D;
30  import org.apache.commons.geometry.euclidean.twod.Vector2D;
31  import org.apache.commons.geometry.examples.jmh.BenchmarkUtils;
32  import org.apache.commons.rng.UniformRandomProvider;
33  import org.apache.commons.rng.sampling.distribution.ZigguratNormalizedGaussianSampler;
34  import org.apache.commons.rng.simple.RandomSource;
35  import org.openjdk.jmh.annotations.Benchmark;
36  import org.openjdk.jmh.annotations.BenchmarkMode;
37  import org.openjdk.jmh.annotations.Fork;
38  import org.openjdk.jmh.annotations.Level;
39  import org.openjdk.jmh.annotations.Measurement;
40  import org.openjdk.jmh.annotations.Mode;
41  import org.openjdk.jmh.annotations.OutputTimeUnit;
42  import org.openjdk.jmh.annotations.Param;
43  import org.openjdk.jmh.annotations.Scope;
44  import org.openjdk.jmh.annotations.Setup;
45  import org.openjdk.jmh.annotations.State;
46  import org.openjdk.jmh.annotations.Warmup;
47  import org.openjdk.jmh.infra.Blackhole;
48  
49  /**
50   * Benchmarks for the Euclidean vector classes.
51   */
52  @BenchmarkMode(Mode.AverageTime)
53  @OutputTimeUnit(TimeUnit.NANOSECONDS)
54  @Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
55  @Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
56  @Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
57  public class VectorPerformance {
58  
59      /**
60       * An array of edge numbers that will produce edge case results from functions:
61       * {@code +/-inf, +/-max, +/-min, +/-0, nan}.
62       */
63      private static final double[] EDGE_NUMBERS = {
64          Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.MAX_VALUE,
65          -Double.MAX_VALUE, Double.MIN_VALUE, -Double.MIN_VALUE, 0.0, -0.0, Double.NaN
66      };
67  
68      /** String constant used to request random double values, excluding NaN and infinity. */
69      private static final String RANDOM = "random";
70  
71      /** String constant used to request a set of double values capable of vector normalization. These
72       * values are similar to those in the {@link #RANDOM} type but specifically exclude 0.
73       */
74      private static final String NORMALIZABLE = "normalizable";
75  
76      /** String constant used to request edge-case double values, which includes NaN, infinity, zero,
77       * and the double min and max values.
78       */
79      private static final String EDGE = "edge";
80  
81      /** Base class for vector inputs.
82       * @param <V> Vector implementation type
83       */
84      @State(Scope.Thread)
85      public abstract static class VectorInputBase<V extends Vector<V>> {
86  
87          /** The dimension of the vector. */
88          private final int dimension;
89  
90          /** Factory function used to create vectors from arrays of doubles. */
91          private final Function<double[], V> vectorFactory;
92  
93          /** The number of vectors in the input list. */
94          @Param({"100", "10000"})
95          private int size;
96  
97          /** The vector for the instance. */
98          private List<V> vectors;
99  
100         /** Create a new instance with the vector dimension.
101          * @param dimension vector dimension
102          * @param vectorFactory function for creating vectors from double arrays
103          */
104         VectorInputBase(final int dimension, final Function<double[], V> vectorFactory) {
105             this.dimension = dimension;
106             this.vectorFactory = vectorFactory;
107         }
108 
109         /** Set up the instance for the benchmark.
110          */
111         @Setup(Level.Iteration)
112         public void setup() {
113             vectors = new ArrayList<>(size);
114 
115             final double[] values = new double[dimension];
116             final DoubleSupplier doubleSupplier = createDoubleSupplier(getType(),
117                     RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP));
118 
119             for (int i = 0; i < size; ++i) {
120                 for (int j = 0; j < dimension; ++j) {
121                     values[j] = doubleSupplier.getAsDouble();
122                 }
123 
124                 vectors.add(vectorFactory.apply(values));
125             }
126         }
127 
128         /** Get the input vectors for the instance.
129          * @return the input vectors for the instance
130          */
131         public List<V> getVectors() {
132             return vectors;
133         }
134 
135         /** Get the type of double values to use in the creation of input vectors.
136          * @return the type of double values to use in the creation of input vectors
137          */
138         public abstract String getType();
139 
140         /** Create a supplier that produces doubles of the given type.
141          * @param type type of doubles to produce
142          * @param rng random provider
143          * @return a supplier that produces doubles of the given type
144          */
145         private DoubleSupplier createDoubleSupplier(final String type, final UniformRandomProvider rng) {
146             switch (type) {
147             case RANDOM:
148                 return () -> BenchmarkUtils.randomDouble(rng);
149             case NORMALIZABLE:
150                 final ZigguratNormalizedGaussianSampler sampler = ZigguratNormalizedGaussianSampler.of(rng);
151                 return () -> {
152                     final double n = sampler.sample();
153                     return n == 0 ? 0.1 : n; // do not return exactly zero
154                 };
155             case EDGE:
156                 return () -> EDGE_NUMBERS[rng.nextInt(EDGE_NUMBERS.length)];
157             default:
158                 throw new IllegalStateException("Invalid input type: " + type);
159             }
160         }
161     }
162 
163     /** Vector input class producing {@link Vector1D} instances with random
164      * double values.
165      */
166     @State(Scope.Thread)
167     public static class VectorInput1D extends VectorInputBase<Vector1D> {
168 
169         /** The type of values to use in the vector. */
170         @Param({RANDOM, EDGE})
171         private String type;
172 
173         /** Default constructor. */
174         public VectorInput1D() {
175             super(1, arr -> Vector1D.of(arr[0]));
176         }
177 
178         /** {@inheritDoc} */
179         @Override
180         public String getType() {
181             return type;
182         }
183     }
184 
185     /** Vector input class producing {@link Vector1D} instances capable of being normalized.
186      */
187     @State(Scope.Thread)
188     public static class NormalizableVectorInput1D extends VectorInputBase<Vector1D> {
189 
190         /** Default constructor. */
191         public NormalizableVectorInput1D() {
192             super(1, arr -> Vector1D.of(arr[0]));
193         }
194 
195         /** {@inheritDoc} */
196         @Override
197         public String getType() {
198             return NORMALIZABLE;
199         }
200     }
201 
202     /** Vector input class producing {@link Vector2D} instances with random
203      * double values.
204      */
205     @State(Scope.Thread)
206     public static class VectorInput2D extends VectorInputBase<Vector2D> {
207 
208         /** The type of values to use in the vector. */
209         @Param({RANDOM, EDGE})
210         private String type;
211 
212         /** Default constructor. */
213         public VectorInput2D() {
214             super(2, Vector2D::of);
215         }
216 
217         /** {@inheritDoc} */
218         @Override
219         public String getType() {
220             return type;
221         }
222     }
223 
224     /** Vector input class producing {@link Vector2D} instances capable of being normalized.
225      */
226     @State(Scope.Thread)
227     public static class NormalizableVectorInput2D extends VectorInputBase<Vector2D> {
228 
229         /** Default constructor. */
230         public NormalizableVectorInput2D() {
231             super(2, Vector2D::of);
232         }
233 
234         /** {@inheritDoc} */
235         @Override
236         public String getType() {
237             return NORMALIZABLE;
238         }
239     }
240 
241     /** Vector input class producing {@link Vector2D} instances with random
242      * double values.
243      */
244     @State(Scope.Thread)
245     public static class VectorInput3D extends VectorInputBase<Vector3D> {
246 
247         /** The type of values to use in the vector. */
248         @Param({RANDOM, EDGE})
249         private String type;
250 
251         /** Default constructor. */
252         public VectorInput3D() {
253             super(3, Vector3D::of);
254         }
255 
256         /** {@inheritDoc} */
257         @Override
258         public String getType() {
259             return type;
260         }
261     }
262 
263     /** Vector input class producing {@link Vector3D} instances capable of being normalized.
264      */
265     @State(Scope.Thread)
266     public static class NormalizableVectorInput3D extends VectorInputBase<Vector3D> {
267 
268         /** Default constructor. */
269         public NormalizableVectorInput3D() {
270             super(3, Vector3D::of);
271         }
272 
273         /** {@inheritDoc} */
274         @Override
275         public String getType() {
276             return NORMALIZABLE;
277         }
278     }
279 
280     /** Run a benchmark test on a function that produces a double.
281      * @param <V> Vector implementation type
282      * @param input vector input
283      * @param bh jmh blackhole for consuming output
284      * @param fn function to call
285      */
286     private static <V extends Vector<V>> void testToDouble(final VectorInputBase<V> input, final Blackhole bh,
287             final ToDoubleFunction<V> fn) {
288         for (final V vec : input.getVectors()) {
289             bh.consume(fn.applyAsDouble(vec));
290         }
291     }
292 
293     /** Run a benchmark test on a function that accepts a vector.
294      * @param <V> Vector implementation type
295      * @param input vector input
296      * @param bh jmh blackhole for consuming output
297      * @param fn function to call
298      */
299     private static <V extends Vector<V>> void testFunction(final VectorInputBase<V> input, final Blackhole bh,
300             final Function<V, ?> fn) {
301         for (final V vec : input.getVectors()) {
302             bh.consume(fn.apply(vec));
303         }
304     }
305 
306     /** Benchmark testing just the overhead of the benchmark harness.
307      * @param input benchmark state input
308      * @param bh jmh blackhole for consuming output
309      */
310     @Benchmark
311     public void baseline(final VectorInput1D input, final Blackhole bh) {
312         testFunction(input, bh, UnaryOperator.identity());
313     }
314 
315     /** Benchmark testing the performance of the {@link Vector1D#norm()} method.
316      * @param input benchmark state input
317      * @param bh jmh blackhole for consuming output
318      */
319     @Benchmark
320     public void norm1D(final VectorInput1D input, final Blackhole bh) {
321         testToDouble(input, bh, Vector1D::norm);
322     }
323 
324     /** Benchmark testing the performance of the {@link Vector2D#norm()} method.
325      * @param input benchmark state input
326      * @param bh jmh blackhole for consuming output
327      */
328     @Benchmark
329     public void norm2D(final VectorInput2D input, final Blackhole bh) {
330         testToDouble(input, bh, Vector2D::norm);
331     }
332 
333     /** Benchmark testing the performance of the {@link Vector3D#norm()} method.
334      * @param input benchmark state input
335      * @param bh jmh blackhole for consuming output
336      */
337     @Benchmark
338     public void norm3D(final VectorInput3D input, final Blackhole bh) {
339         testToDouble(input, bh, Vector3D::norm);
340     }
341 
342     /** Benchmark testing the performance of the {@link Vector1D#normalize()} method.
343      * @param input benchmark state input
344      * @param bh jmh blackhole for consuming output
345      */
346     @Benchmark
347     public void normalize1D(final NormalizableVectorInput1D input, final Blackhole bh) {
348         testFunction(input, bh, Vector1D::normalize);
349     }
350 
351     /** Benchmark testing the performance of the {@link Vector1D#normalizeOrNull()} method.
352      * @param input benchmark state input
353      * @param bh jmh blackhole for consuming output
354      */
355     @Benchmark
356     public void normalizeOrNull1D(final VectorInput1D input, final Blackhole bh) {
357         testFunction(input, bh, v -> v.normalizeOrNull());
358     }
359 
360     /** Benchmark testing the performance of the {@link Vector2D#normalize()}
361      * method.
362      * @param input benchmark state input
363      * @param bh jmh blackhole for consuming output
364      */
365     @Benchmark
366     public void normalize2D(final NormalizableVectorInput2D input, final Blackhole bh) {
367         testFunction(input, bh, Vector2D::normalize);
368     }
369 
370     /** Benchmark testing the performance of the {@link Vector2D#normalizeOrNull()}
371      * method.
372      * @param input benchmark state input
373      * @param bh jmh blackhole for consuming output
374      */
375     @Benchmark
376     public void normalizeOrNull2D(final VectorInput2D input, final Blackhole bh) {
377         testFunction(input, bh, v -> v.normalizeOrNull());
378     }
379 
380     /** Benchmark testing the performance of the {@link Vector3D#normalize()}
381      * method.
382      * @param input benchmark state input
383      * @param bh jmh blackhole for consuming output
384      */
385     @Benchmark
386     public void normalize3D(final NormalizableVectorInput3D input, final Blackhole bh) {
387         testFunction(input, bh, Vector3D::normalize);
388     }
389 
390     /** Benchmark testing the performance of the {@link Vector3D#normalizeOrNull()}
391      * method.
392      * @param input benchmark state input
393      * @param bh jmh blackhole for consuming output
394      */
395     @Benchmark
396     public void normalizeOrNull3D(final VectorInput3D input, final Blackhole bh) {
397         testFunction(input, bh, v -> v.normalizeOrNull());
398     }
399 }