Skip to content
Permalink
Browse files
improvements to DoubleFormats performance
  • Loading branch information
darkma773r committed Jun 12, 2021
1 parent af2b57c commit b9ff14e03b06f5b4ca1881ec905d64926ba09f21
Showing 6 changed files with 323 additions and 74 deletions.
@@ -56,6 +56,11 @@
<artifactId>commons-geometry-euclidean</artifactId>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-geometry-io-core</artifactId>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-rng-simple</artifactId>
@@ -22,22 +22,36 @@
*/
public final class BenchmarkUtils {

/** Default min exponent for random double values. */
public static final int DEFAULT_MIN_EXP = -64;

/** Default max exponent for random double values. */
public static final int DEFAULT_MAX_EXP = 64;

/** Utility class; no instantiation. */
private BenchmarkUtils() {}

/** 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.
/** Creates a random double number with a random sign and mantissa and a large, default
* range for the exponent. The numbers will not be uniform over the range.
* @param rng random number generator
* @return the random number
*/
public static double randomDouble(final UniformRandomProvider rng) {
return randomDouble(DEFAULT_MIN_EXP, DEFAULT_MAX_EXP, rng);
}

/** Create a random double value with exponent in the range {@code [minExp, maxExp]}.
* @param minExp minimum exponent; must be less than {@code maxExp}
* @param maxExp maximum exponent; must be greater than {@code minExp}
* @param rng random number generator
* @return random double
*/
public static double randomDouble(final int minExp, final int maxExp, final UniformRandomProvider 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;
final long exp = rng.nextInt(maxExp - minExp + 1) + minExp + 1023;
return Double.longBitsToDouble(bits | (exp << 52));
}

@@ -46,13 +60,24 @@ public static double randomDouble(final UniformRandomProvider rng) {
* @param len array length
* @return array containing {@code len} random doubles
*/
public static double[] randomDoubleArray(final UniformRandomProvider rng, final int len) {
final double[] arr = new double[len];
public static double[] randomDoubleArray(final int len, final UniformRandomProvider rng) {
return randomDoubleArray(len, DEFAULT_MIN_EXP, DEFAULT_MAX_EXP, rng);
}

/** Create an array with the given length containing random doubles with exponents in the range
* {@code [minExp, maxExp]}.
* @param len array length
* @param minExp minimum exponent; must be less than {@code maxExp}
* @param maxExp maximum exponent; must be greater than {@code minExp}
* @param rng random number generator
* @return array of random doubles
*/
public static double[] randomDoubleArray(final int len, final int minExp, final int maxExp,
final UniformRandomProvider rng) {
final double[] arr = new double[len];
for (int i = 0; i < arr.length; ++i) {
arr[i] = randomDouble(rng);
arr[i] = randomDouble(minExp, maxExp, rng);
}

return arr;
}
}
@@ -111,7 +111,7 @@ public AffineTransformMatrix1D getTransform() {
public void setup() {
final UniformRandomProvider rand = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP);

transform = AffineTransformMatrix1D.of(BenchmarkUtils.randomDoubleArray(rand, 2));
transform = AffineTransformMatrix1D.of(BenchmarkUtils.randomDoubleArray(2, rand));
}
}

@@ -135,7 +135,7 @@ public AffineTransformMatrix2D getTransform() {
public void setup() {
final UniformRandomProvider rand = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP);

transform = AffineTransformMatrix2D.of(BenchmarkUtils.randomDoubleArray(rand, 6));
transform = AffineTransformMatrix2D.of(BenchmarkUtils.randomDoubleArray(6, rand));
}
}

@@ -159,7 +159,7 @@ public AffineTransformMatrix3D getTransform() {
public void setup() {
final UniformRandomProvider rand = RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP);

transform = AffineTransformMatrix3D.of(BenchmarkUtils.randomDoubleArray(rand, 12));
transform = AffineTransformMatrix3D.of(BenchmarkUtils.randomDoubleArray(12, rand));
}
}

@@ -0,0 +1,183 @@
/*
* 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.geometry.examples.jmh.io.core;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.concurrent.TimeUnit;
import java.util.function.DoubleFunction;

import org.apache.commons.geometry.examples.jmh.BenchmarkUtils;
import org.apache.commons.geometry.io.core.utils.DoubleFormats;
import org.apache.commons.rng.simple.RandomSource;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Level;
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 org.openjdk.jmh.infra.Blackhole;

/** Benchmarks for the {@link DoubleFormats} class.
*/
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(value = 1, jvmArgs = {"-server", "-Xms512M", "-Xmx512M"})
public class DoubleFormatsPerformance {

/** Benchmark input providing a source of random double values. */
@State(Scope.Thread)
public static class DoubleInput {

/** The number of doubles in the input array. */
@Param({"10000"})
private int size;

/** Minimum base 2 exponent for random input doubles. */
@Param("-20")
private int minExp;

/** Maximum base 2 exponent for random input doubles. */
@Param("20")
private int maxExp;

/** Double input array. */
private double[] input;

/** Get the input doubles.
* @return the input doubles
*/
public double[] getInput() {
return input;
}

/** Set up the instance for the benchmark. */
@Setup(Level.Iteration)
public void setup() {
input = BenchmarkUtils.randomDoubleArray(size, minExp, maxExp,
RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP));
}
}

/** Run a benchmark test on a function accepting a double argument.
* @param <T> function output type
* @param input double array
* @param bh jmh blackhole for consuming output
* @param fn function to call
*/
private static <T> void runDoubleFunction(final DoubleInput input, final Blackhole bh,
final DoubleFunction<T> fn) {
for (final double d : input.getInput()) {
bh.consume(fn.apply(d));
}
}

/** Benchmark testing just the overhead of the benchmark harness.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void baseline(final DoubleInput input, final Blackhole bh) {
runDoubleFunction(input, bh, d -> "");
}

/** Benchmark testing the {@link Double#toString()} method.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleToString(final DoubleInput input, final Blackhole bh) {
runDoubleFunction(input, bh, Double::toString);
}

/** Benchmark testing the {@link String#format(String, Object...)} method.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void stringFormat(final DoubleInput input, final Blackhole bh) {
runDoubleFunction(input, bh, d -> String.format("%d", d));
}

/** Benchmark testing the BigDecimal formatting performance.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void bigDecimal(final DoubleInput input, final Blackhole bh) {
final DoubleFunction<String> fn = d -> BigDecimal.valueOf(d)
.setScale(3, RoundingMode.HALF_EVEN)
.stripTrailingZeros()
.toString();
runDoubleFunction(input, bh, fn);
}

/** Benchmark testing the {@link DecimalFormat} class.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void decimalFormat(final DoubleInput input, final Blackhole bh) {
final DecimalFormat fmt = new DecimalFormat("0.###");
runDoubleFunction(input, bh, fmt::format);
}

/** Benchmark testing the {@link DoubleFormats#createDefault(int, int)} method.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatsDefault(final DoubleInput input, final Blackhole bh) {
runDoubleFunction(input, bh, DoubleFormats.createDefault(0, -3));
}

/** Benchmark testing the {@link DoubleFormats#createPlain(int, int)} method.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatsPlain(final DoubleInput input, final Blackhole bh) {
runDoubleFunction(input, bh, DoubleFormats.createPlain(0, -3));
}

/** Benchmark testing the {@link DoubleFormats#createScientific(int, int)} method.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatsScientific(final DoubleInput input, final Blackhole bh) {
runDoubleFunction(input, bh, DoubleFormats.createScientific(0, -3));
}

/** Benchmark testing the {@link DoubleFormats#createEngineering(int, int)} method.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatsEngineering(final DoubleInput input, final Blackhole bh) {
runDoubleFunction(input, bh, DoubleFormats.createEngineering(0, -3));
}
}
@@ -0,0 +1,23 @@
/*
* 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.
*/

/**
* Benchmarks for the components in the {@code org.apache.commons.geometry.io.core}
* package.
*/

package org.apache.commons.geometry.examples.jmh.io.core;

0 comments on commit b9ff14e

Please sign in to comment.