blob: b22daf769ca9ff8d21a1bb02ab8fcf37b9c9cefd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.numbers.examples.jmh.complex;
import org.apache.commons.numbers.complex.Complex;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import java.util.Arrays;
import java.util.SplittableRandom;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.function.ToDoubleFunction;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
/**
* Executes a benchmark to measure the speed of operations in the {@link Complex} class.
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
@Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
public class ComplexPerformance {
/**
* An array of edge numbers that will produce edge case results from functions:
* {@code +/-inf, +/-max, +/-min, +/-0, nan}.
*/
private static final double[] EDGE_NUMBERS = {
Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.MAX_VALUE,
-Double.MAX_VALUE, Double.MIN_VALUE, -Double.MIN_VALUE, 0.0, -0.0, Double.NaN};
/**
* Contains the size of numbers.
*/
@State(Scope.Benchmark)
public static class ComplexNumberSize {
/**
* The size of the data.
*/
@Param({"1000"})
private int size;
/**
* Gets the size.
*
* @return the size
*/
public int getSize() {
return size;
}
}
/**
* Contains an array of complex numbers.
*/
@State(Scope.Benchmark)
public static class ComplexNumbers extends ComplexNumberSize {
/** The numbers. */
protected Complex[] numbers;
/**
* The type of the data.
*/
@Param({"cis", "random", "edge"})
private String type;
/**
* Gets the numbers.
*
* @return the numbers
*/
public Complex[] getNumbers() {
return numbers;
}
/**
* Create the complex numbers.
*/
@Setup
public void setup() {
numbers = createNumbers(new SplittableRandom());
}
/**
* Creates the numbers.
*
* @param rng Random number generator.
* @return the random complex number
*/
Complex[] createNumbers(SplittableRandom rng) {
Supplier<Complex> generator;
if ("cis".equals(type)) {
generator = () -> Complex.ofCis(rng.nextDouble() * 2 * Math.PI);
} else if ("random".equals(type)) {
generator = () -> Complex.ofCartesian(createRandomNumber(rng), createRandomNumber(rng));
} else if ("edge".equals(type)) {
generator = () -> Complex.ofCartesian(createEdgeNumber(rng), createEdgeNumber(rng));
} else {
throw new IllegalStateException("Unknown number type: " + type);
}
return Stream.generate(generator).limit(getSize()).toArray(Complex[]::new);
}
}
/**
* Contains two arrays of complex numbers.
*/
@State(Scope.Benchmark)
public static class TwoComplexNumbers extends ComplexNumbers {
/** The numbers. */
private Complex[] numbers2;
/**
* Gets the second set of numbers.
*
* @return the numbers
*/
public Complex[] getNumbers2() {
return numbers2;
}
/**
* Create the complex numbers.
*/
@Override
@Setup
public void setup() {
// Do not call super.setup() so we recycle the RNG and avoid duplicates
final SplittableRandom rng = new SplittableRandom();
numbers = createNumbers(rng);
numbers2 = createNumbers(rng);
}
}
/**
* Contains an array of complex numbers and an array of real numbers.
*/
@State(Scope.Benchmark)
public static class ComplexAndRealNumbers extends ComplexNumbers {
/** The numbers. */
private double[] numbers2;
/**
* Gets the second set of numbers.
*
* @return the numbers
*/
public double[] getNumbers2() {
return numbers2;
}
/**
* Create the complex numbers.
*/
@Override
@Setup
public void setup() {
// Do not call super.setup() so we recycle the RNG and avoid duplicates
final SplittableRandom rng = new SplittableRandom();
numbers = createNumbers(rng);
numbers2 = Arrays.stream(createNumbers(rng)).mapToDouble(Complex::real).toArray();
}
}
/**
* Define a function between a complex and real number.
*/
private interface ComplexRealFunction {
/**
* Applies this function to the given arguments.
*
* @param z the complex argument
* @param x the real argument
* @return the function result
*/
Complex apply(Complex z, double x);
}
/**
* Creates a random double number with a random sign and mantissa and a large range for
* the exponent. The numbers will not be uniform over the range.
*
* @param rng Random number generator.
* @return the random number
*/
private static double createRandomNumber(SplittableRandom rng) {
// Create random doubles using random bits in the sign bit and the mantissa.
// Then create an exponent in the range -64 to 64. Thus the sum product
// of 4 max or min values will not over or underflow.
final long mask = ((1L << 52) - 1) | 1L << 63;
final long bits = rng.nextLong() & mask;
// The exponent must be unsigned so + 1023 to the signed exponent
final long exp = rng.nextInt(129) - 64 + 1023;
return Double.longBitsToDouble(bits | (exp << 52));
}
/**
* Creates a random double number that will be an edge case:
* {@code +/-inf, +/-max, +/-min, +/-0, nan}.
*
* @param rng Random number generator.
* @return the random number
*/
private static double createEdgeNumber(SplittableRandom rng) {
return EDGE_NUMBERS[rng.nextInt(EDGE_NUMBERS.length)];
}
/**
* Apply the function to all the numbers.
*
* @param numbers Numbers.
* @param fun Function.
* @return the result of the function.
*/
private static boolean[] apply(Complex[] numbers, Predicate<Complex> fun) {
final boolean[] result = new boolean[numbers.length];
for (int i = 0; i < numbers.length; i++) {
result[i] = fun.test(numbers[i]);
}
return result;
}
/**
* Apply the function to all the numbers.
*
* @param numbers Numbers.
* @param fun Function.
* @return the result of the function.
*/
private static double[] apply(Complex[] numbers, ToDoubleFunction<Complex> fun) {
final double[] result = new double[numbers.length];
for (int i = 0; i < numbers.length; i++) {
result[i] = fun.applyAsDouble(numbers[i]);
}
return result;
}
/**
* Apply the function to all the numbers.
*
* @param numbers Numbers.
* @param fun Function.
* @return the result of the function.
*/
private static Complex[] apply(Complex[] numbers, UnaryOperator<Complex> fun) {
final Complex[] result = new Complex[numbers.length];
for (int i = 0; i < numbers.length; i++) {
result[i] = fun.apply(numbers[i]);
}
return result;
}
/**
* Apply the function to the paired numbers.
*
* @param numbers First numbers of the pairs.
* @param numbers2 Second numbers of the pairs.
* @param fun Function.
* @return the result of the function.
*/
private static Complex[] apply(Complex[] numbers, Complex[] numbers2,
BiFunction<Complex, Complex, Complex> fun) {
final Complex[] result = new Complex[numbers.length];
for (int i = 0; i < numbers.length; i++) {
result[i] = fun.apply(numbers[i], numbers2[i]);
}
return result;
}
/**
* Apply the function to the paired numbers.
*
* @param numbers First numbers of the pairs.
* @param numbers2 Second numbers of the pairs.
* @param fun Function.
* @return the result of the function.
*/
private static Complex[] apply(Complex[] numbers, double[] numbers2,
ComplexRealFunction fun) {
final Complex[] result = new Complex[numbers.length];
for (int i = 0; i < numbers.length; i++) {
result[i] = fun.apply(numbers[i], numbers2[i]);
}
return result;
}
/**
* Identity function. This can be used to measure overhead of object array creation.
*
* @param z Complex number.
* @return the complex number
*/
private static Complex identity(Complex z) {
return z;
}
/**
* Copy function. This can be used to measure overhead of object array creation plus
* new Complex creation.
*
* @param z Complex number.
* @return a copy of the complex number
*/
private static Complex copy(Complex z) {
return Complex.ofCartesian(z.real(), z.imag());
}
// Benchmark methods.
//
// The methods are partially documented as the names are self-documenting.
// CHECKSTYLE: stop JavadocMethod
// CHECKSTYLE: stop DesignForExtension
//
// Benchmarks use function references to perform different operations on the complex numbers.
// Tests show that explicit programming of the same benchmarks run in the same time.
// For reference examples are provided for the fastest operations: real() and conj().
/**
* Explicit benchmark without using a method reference.
* This should run in the same time as {@link #real(ComplexNumbers)}.
* This is commented out as it exists for reference purposes.
*/
//@Benchmark
public double[] real2(ComplexNumbers numbers) {
final Complex[] z = numbers.getNumbers();
final double[] result = new double[z.length];
for (int i = 0; i < z.length; i++) {
result[i] = z[i].real();
}
return result;
}
/**
* Explicit benchmark without using a method reference.
* This should run in the same time as {@link #conj(ComplexNumbers)}.
* This is commented out as it exists for reference purposes.
*/
//@Benchmark
public Complex[] conj2(ComplexNumbers numbers) {
final Complex[] z = numbers.getNumbers();
final Complex[] result = new Complex[z.length];
for (int i = 0; i < z.length; i++) {
result[i] = z[i].conj();
}
return result;
}
/**
* Baseline the creation of the new array of numbers.
* This contains the baseline JMH overhead for all the benchmarks that create complex numbers.
* All other methods are expected to be slower than this.
*/
@Benchmark
public Complex[] baselineNewArray(ComplexNumberSize numberSize) {
return new Complex[numberSize.getSize()];
}
/**
* Baseline the creation of a copy array of numbers.
* This is commented out as it provides no information other than to demonstrate that
* {@link #baselineCopy(ComplexNumbers)} is not being optimised to a single array copy
* operation.
*/
//@Benchmark
public Complex[] baselineCopyArray(ComplexNumbers numbers) {
return Arrays.copyOf(numbers.getNumbers(), numbers.getNumbers().length);
}
/**
* Baseline the creation of the new array of numbers with the same complex number (an identity).
*
* <p>Note: This runs much faster than {@link #baselineCopy(ComplexNumbers)}. This is
* attributed to the identity function not requiring that the fields of the
* complex are accessed unlike all other methods that do computations on the real and/or
* imaginary parts. The method is slower than a creation of a new empty array or a
* copy array thus contains the loop overhead of the benchmarks that create new numbers.
*
* @see #baselineNewArray(ComplexNumberSize)
* @see #baselineCopyArray(ComplexNumbers)
*/
@Benchmark
public Complex[] baselineIdentity(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), ComplexPerformance::identity);
}
/**
* Baseline the creation of the new array of numbers with a copy complex number. This
* measures the overhead of creation of new complex numbers including field access
* to the real and imaginary parts.
*/
@Benchmark
public Complex[] baselineCopy(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), ComplexPerformance::copy);
}
// Unary operations that return a boolean
@Benchmark
public boolean[] isNaN(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::isNaN);
}
@Benchmark
public boolean[] isInfinite(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::isInfinite);
}
@Benchmark
public boolean[] isFinite(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::isFinite);
}
// Unary operations that return a double
@Benchmark
public double[] real(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::real);
}
@Benchmark
public double[] imag(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::imag);
}
@Benchmark
public double[] abs(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::abs);
}
@Benchmark
public double[] arg(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::arg);
}
@Benchmark
public double[] norm(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::norm);
}
// Unary operations that return a complex number
@Benchmark
public Complex[] conj(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::conj);
}
@Benchmark
public Complex[] negate(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::negate);
}
@Benchmark
public Complex[] proj(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::proj);
}
@Benchmark
public Complex[] cos(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::cos);
}
@Benchmark
public Complex[] cosh(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::cosh);
}
@Benchmark
public Complex[] exp(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::exp);
}
@Benchmark
public Complex[] log(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::log);
}
@Benchmark
public Complex[] log10(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::log10);
}
@Benchmark
public Complex[] sin(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::sin);
}
@Benchmark
public Complex[] sinh(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::sinh);
}
@Benchmark
public Complex[] sqrt(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::sqrt);
}
@Benchmark
public Complex[] tan(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::tan);
}
@Benchmark
public Complex[] tanh(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::tanh);
}
@Benchmark
public Complex[] acos(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::acos);
}
@Benchmark
public Complex[] acosh(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::acosh);
}
@Benchmark
public Complex[] asin(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::asin);
}
@Benchmark
public Complex[] asinh(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::asinh);
}
@Benchmark
public Complex[] atan(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::atan);
}
@Benchmark
public Complex[] atanh(ComplexNumbers numbers) {
return apply(numbers.getNumbers(), Complex::atanh);
}
// Binary operations on two complex numbers.
@Benchmark
public Complex[] pow(TwoComplexNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::pow);
}
@Benchmark
public Complex[] multiply(TwoComplexNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::multiply);
}
@Benchmark
public Complex[] divide(TwoComplexNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::divide);
}
@Benchmark
public Complex[] add(TwoComplexNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::add);
}
@Benchmark
public Complex[] subtract(TwoComplexNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::subtract);
}
// Binary operations on a complex and a real number.
// These only benchmark methods on the real component as the
// following are expected to be the same speed as the real-only operations
// given the equivalent primitive operations:
// - multiplyImaginary
// - divideImaginary
// - addImaginary
// - subtractImaginary
// - subtractFrom
// - subtractFromImaginary
@Benchmark
public Complex[] powReal(ComplexAndRealNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::pow);
}
@Benchmark
public Complex[] multiplyReal(ComplexAndRealNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::multiply);
}
@Benchmark
public Complex[] divideReal(ComplexAndRealNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::divide);
}
@Benchmark
public Complex[] addReal(ComplexAndRealNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::add);
}
@Benchmark
public Complex[] subtractReal(ComplexAndRealNumbers numbers) {
return apply(numbers.getNumbers(), numbers.getNumbers2(), Complex::subtract);
}
}