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 }