blob: 4b584915224b4cbaf46096c0160051e1382064c2 [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.text.jmh;
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.rng.UniformRandomProvider;
import org.apache.commons.rng.simple.RandomSource;
import org.apache.commons.text.numbers.DoubleFormat;
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 DoubleFormat} 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 DoubleFormatPerformance {
/** 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("-100")
private int minExp;
/** Maximum base 2 exponent for random input doubles. */
@Param("100")
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 = randomDoubleArray(size, minExp, maxExp,
RandomSource.create(RandomSource.XO_RO_SHI_RO_128_PP));
}
}
/** Decimal format pattern for plain output. */
private static final String PLAIN_PATTERN = "0.0##";
/** Decimal format pattern for plain output with thousands grouping. */
private static final String PLAIN_GROUPED_PATTERN = "#,##0.0##";
/** Decimal format pattern for scientific output. */
private static final String SCI_PATTERN = "0.0##E0";
/** Decimal format pattern for engineering output. */
private static final String ENG_PATTERN = "##0.0##E0";
/** 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
*/
private 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.
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(maxExp - minExp + 1) + minExp + 1023;
return Double.longBitsToDouble(bits | exp << 52);
}
/** 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
*/
private 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(minExp, maxExp, rng);
}
return arr;
}
/** 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 -> "0.0");
}
/** 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 with engineering format.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void decimalFormatEngineering(final DoubleInput input, final Blackhole bh) {
final DecimalFormat fmt = new DecimalFormat(ENG_PATTERN);
runDoubleFunction(input, bh, fmt::format);
}
/** Benchmark testing the {@link DecimalFormat} class.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void decimalFormatPlain(final DoubleInput input, final Blackhole bh) {
final DecimalFormat fmt = new DecimalFormat(PLAIN_PATTERN);
runDoubleFunction(input, bh, fmt::format);
}
/** Benchmark testing the {@link DecimalFormat} class with thousands grouping.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void decimalFormatPlainGrouped(final DoubleInput input, final Blackhole bh) {
final DecimalFormat fmt = new DecimalFormat(PLAIN_GROUPED_PATTERN);
runDoubleFunction(input, bh, fmt::format);
}
/** Benchmark testing the {@link DecimalFormat} class with scientific format.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void decimalFormatScientific(final DoubleInput input, final Blackhole bh) {
final DecimalFormat fmt = new DecimalFormat(SCI_PATTERN);
runDoubleFunction(input, bh, fmt::format);
}
/** Benchmark testing the {@link DoubleFormat#ENGINEERING} format.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatEngineering(final DoubleInput input, final Blackhole bh) {
final DoubleFunction<String> fmt = DoubleFormat.ENGINEERING.builder()
.maxPrecision(6)
.alwaysIncludeExponent(true)
.build();
runDoubleFunction(input, bh, fmt);
}
/** Benchmark testing the {@link DoubleFormat#PLAIN} format.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatPlain(final DoubleInput input, final Blackhole bh) {
final DoubleFunction<String> fmt = DoubleFormat.PLAIN.builder()
.minDecimalExponent(-3)
.build();
runDoubleFunction(input, bh, fmt);
}
/** Benchmark testing the {@link DoubleFormat#PLAIN} format with
* thousands grouping.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatPlainGrouped(final DoubleInput input, final Blackhole bh) {
final DoubleFunction<String> fmt = DoubleFormat.PLAIN.builder()
.minDecimalExponent(-3)
.groupThousands(true)
.build();
runDoubleFunction(input, bh, fmt);
}
/** Benchmark testing the {@link DoubleFormat#SCIENTIFIC} format.
* @param input benchmark state input
* @param bh jmh blackhole for consuming output
*/
@Benchmark
public void doubleFormatScientific(final DoubleInput input, final Blackhole bh) {
final DoubleFunction<String> fmt = DoubleFormat.SCIENTIFIC.builder()
.maxPrecision(4)
.alwaysIncludeExponent(true)
.build();
runDoubleFunction(input, bh, fmt);
}
/** 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("%f", d));
}
}