jdk/test/java/text/Format/DecimalFormat/FormatMicroBenchmark.java
author david
Wed, 18 Nov 2015 14:51:10 +0100
changeset 35246 05ced3076fd8
parent 14015 f771d5fb3b27
permissions -rw-r--r--
8145092: Use Unified Logging for the GC logging Summary: JEP-271 Reviewed-by: sjohanss, brutisso

/*
 * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

/*
 * @test
 * @bug 7050528
 * @summary Set of micro-benchmarks testing throughput of java.text.DecimalFormat.format()
 * @author Olivier Lagneau
 * @run main FormatMicroBenchmark
 */

/* This is a set of micro-benchmarks testing throughput of java.text.DecimalFormat.format().
 * It never fails.
 *
 * Usage and arguments:
 *  - Run with no argument skips the whole benchmark and exits.
 *  - Run with "-help" as first argument calls the usage() method and exits.
 *  - Run with "-doit" runs the benchmark with summary details.
 *  - Run with "-verbose" provides additional details on the run.
 *
 * Example run :
 *   java -Xms500m -Xmx500m -XX:NewSize=400m FormatMicroBenchmark -doit -verbose
 *
 * Running with jtreg:
 *  The jtreg header "run" tag options+args must be changed to avoid skipping
 *  the execution. here is an example of run options:
 *  "main/othervm -Xms500m -Xmx500m -XX:NewSize=400m FormatMicroBenchmark -doit"
 *
 * Note:
 *  - Vm options -Xms, -Xmx, -XX:NewSize must be set correctly for
 *    getting reliable numbers. Otherwise GC activity may corrupt results.
 *    As of jdk80b48 using "-Xms500m -Xmx500m -XX:NewSize=400m" covers
 *    all cases.
 *  - Optionally using "-Xlog:gc" option provides information that
 *    helps checking any GC activity while benches are run.
 *
 * Vm Options:
 *  - Vm options to use (as of jdk80b48):
 *     fast-path case :     -Xms128m -Xmx128m -XX:NewSize=100m
 *     non fast-path case:  -Xms500m -Xmx500m -XX:NewSize=400m
 *    or use worst case (non fast-path above) with both types of algorithm.
 *
 *  - use -Xlog:gc to verify memory consumption of the benchmarks.
 *    (See "Checking Memory Consumption" below).
 *
 * Description:
 *
 *  Fast-path algorithm for format(double...)  call stack is very different  of
 *  the standard call stack. Where the  standard algorithm for formating double
 *  uses internal class sun.misc.FloatingDecimal and its dtoa(double) method to
 *  provide digits,  fast-path embeds its own  algorithm for  binary to decimal
 *  string conversion.
 *
 *  FloatingDecimal always converts completely  the passed double to  a string.
 *  Fast-path converts  only to the needed digits  since it follows constraints
 *  on both the pattern rule,  the  DecimalFormat instance properties, and  the
 *  passed double.
 *
 *  Micro benchmarks below measure  the throughput for formating double  values
 *  using NumberFormat.format(double)  call stack.  The  standard DecimalFormat
 *  call stack as well as the  fast-path algorithm implementation are sensitive
 *  to the nature of the passed double values regarding throughput performance.
 *
 *  These benchmarks are useful both  for measuring the global performance gain
 *  of fast-path and to check that any modification done on fast-path algorithm
 *  does not bring any regression in the performance boost of fast-path.
 *
 *  Note  that these benchmarks  will provide numbers  without any knowledge of
 *  the  implementation of DecimalFormat class. So  to check regression any run
 *  should be compared to another reference run with  a previous JDK, wether or
 *  not this previous reference JDK contains fast-path implementation.
 *
 *  The eight benchmarks below are dedicated to measure throughput on different
 *  kinds of double that all fall in the fast-path case (all in Integer range):
 *
 *  - Integer case : used double values are all "integer-like" (ex: -12345.0).
 *    This is the benchFormatInteger micro-benchmark.
 *
 *  - Fractional case : double values are "fractional" (ex: -0.12345).
 *    This is the benchFormatFractional micro-benchmark.
 *
 *  - Small integral case : like Integer case but double values are all limited
 *    in their magnitude, from -500.0 to 500.0 if the number of iterations N is
 *    set to 500000.
 *    This is the benchFormatSmallIntegral micro-benchmark.
 *
 *  - Fractional All Nines : doubles values have fractional part that is very
 *    close to "999" (decimal pattern), or "99" (currency pattern),
 *    or "0000...".
 *    This is the benchFormatFractionalAllNines micro-benchmark.
 *
 *  - All Nines : double values are such that both integral and fractional
 *    part consist only of '9' digits. None of these values are rounded up.
 *    This is the benchFormatAllNines micro-benchmark.
 *
 *  - Fair simple case : calling J the loop variable and iterating over
 *    the N number of iterations, used double values are computed as
 *    d = (double) J + J*seed
 *    where seed is a very small value that adds a fractional part and adds a
 *    small number to integral part. Provides fairly distributed double values.
 *    This is the benchFormatFairSimple micro-benchmark.
 *
 *  - Fair case : this is a combination of small integral case and fair simple
 *    case. Double values are limited in their magnitude but follow a parabolic
 *    curve y = x**2 / K, keeping large magnitude only for large values of J.
 *    The intent is trying to reproduce a distribution of double values as could
 *    be found in a business application, with most values in either the low
 *    range or the high range.
 *    This is the benchFormatFair micro-benchmark.
 *
 *  - Tie cases: values are very close to a tie case (iii...ii.fff5)
 *    That is the worst situation that can happen for Fast-path algorithm when
 *    considering throughput.
 *    This is the benchFormatTie micro-benchmark.
 *
 *  For  all  of  the micro-benchmarks,  the  throughput load   of the eventual
 *  additional computations inside the loop is calculated  prior to running the
 *  benchmark, and provided in the output.  That may be  useful since this load
 *  may vary for each architecture or machine configuration.
 *
 *  The "-verbose" flag,  when set, provides the  throughput  load numbers, the
 *  time spent for  each run of  a benchmark, as  well as an estimation  of the
 *  memory consumed  by the  runs.  Beware of  incremental  GCs, see  "Checking
 *  Memory  Consumption" section below. Every run   should be done with correct
 *  ms, mx, and NewSize vm options to get fully reliable numbers.
 *
 *  The output provides the  mean time needed for  a benchmark after the server
 *  jit compiler has done its optimization work if  any. Thus only the last but
 *  first three runs are taken into account in the time measurement (server jit
 *  compiler shows  to have  done full  optimization  in  most cases  after the
 *  second run, given a base number of iterations set to 500000).
 *
 *  The program cleans up memory (stabilizeMemory() method) between each run of
 *  the benchmarks to make sure that  no garbage collection activity happens in
 *  measurements. However that does not  preclude incremental GCs activity that
 *  may  happen during the micro-benchmark if  -Xms, -Xmx, and NewSize options
 *  have not been tuned and set correctly.
 *
 * Checking Memory Consumption:
 *
 *  For getting confidence  in the throughput numbers, there  must not give any
 *  GC activity during the benchmark runs. That  means that specific VM options
 *  related to memory must be tuned for any given implementation of the JDK.
 *
 *  Running with "-verbose" arguments will provide  clues of the memory consumed
 *  but  is   not enough,  since  any   unexpected  incremental  GC  may  lower
 *  artificially the estimation of the memory consumption.
 *
 *  Options to  set are -Xms, -Xmx,  -XX:NewSize, plus -Xlog:gc to evaluate
 *  correctly  the  values of  these options. When  running "-verbose", varying
 *  numbers reported for memory consumption may  indicate bad choices for these
 *  options.
 *
 *  For jdk80b25, fast-path shows a consuption of ~60Mbs for 500000 iterations
 *  while a jdk without fast-path will consume ~260Mbs for each benchmark run.
 *  Indeed these values will vary depending on the jdk used.
 *
 *  Correct option settings found jdk80b48 were :
 *     fast-path :     -Xms128m -Xmx128m -XX:NewSize=100m
 *     non fast-path : -Xms500m -Xmx500m -XX:NewSize=400m
 *  Greater values can be provided safely but not smaller ones.
 * ----------------------------------------------------------------------
 */

import java.util.*;
import java.text.NumberFormat;
import java.text.DecimalFormat;

public class FormatMicroBenchmark {

    // The number of times the bench method will be run (must be at least 4).
    private static final int NB_RUNS = 20;

    // The bench* methods below all iterates over [-MAX_RANGE , +MAX_RANGE] integer values.
    private static final int MAX_RANGE = 500000;

    // Flag for more details on each bench run (default is no).
    private static boolean Verbose = false;

    // Should we really execute the benches ? (no by default).
    private static boolean DoIt = false;

    // Prints out a message describing how to run the program.
    private static void usage() {
        System.out.println(
            "This is a set of micro-benchmarks testing throughput of " +
            "java.text.DecimalFormat.format(). It never fails.\n\n" +
            "Usage and arguments:\n" +
            " - Run with no argument skips the whole benchmark and exits.\n" +
            " - Run with \"-help\" as first argument prints this message and exits.\n" +
            " - Run with \"-doit\" runs the benchmark with summary details.\n" +
            " - Run with \"-verbose\" provides additional details on the run.\n\n" +
            "Example run :\n" +
            "   java -Xms500m -Xmx500m -XX:NewSize=400m FormatMicroBenchmark -doit -verbose\n\n" +
            "Note: \n" +
            " - Vm options -Xms, -Xmx, -XX:NewSize must be set correctly for \n" +
            "   getting reliable numbers. Otherwise GC activity may corrupt results.\n" +
            "   As of jdk80b48 using \"-Xms500m -Xmx500m -XX:NewSize=400m\" covers \n" +
            "   all cases.\n" +
            " - Optionally using \"-Xlog:gc\" option provides information that \n" +
            "   helps checking any GC activity while benches are run.\n\n" +
            "Look at the heading comments and description in source code for " +
            "detailed information.\n");
    }

    /* We will call stabilizeMemory before each call of benchFormat***().
     * This in turn tries to clean up as much memory as possible.
     * As a safe bound we limit number of System.gc() calls to 10,
     * but most of the time two calls to System.gc() will be enough.
     * If memory reporting is asked for, the method returns the difference
     * of free memory between entering an leaving the method.
     */
    private static long stabilizeMemory(boolean reportConsumedMemory) {
        final long oneMegabyte = 1024L * 1024L;

        long refMemory = 0;
        long initialMemoryLeft = Runtime.getRuntime().freeMemory();
        long currMemoryLeft = initialMemoryLeft;
        int nbGCCalls = 0;

        do {
            nbGCCalls++;

            refMemory = currMemoryLeft;
            System.gc();
            currMemoryLeft = Runtime.getRuntime().freeMemory();

        } while ((Math.abs(currMemoryLeft - refMemory) > oneMegabyte) &&
                 (nbGCCalls < 10));

        if (Verbose &&
            reportConsumedMemory)
            System.out.println("Memory consumed by previous run : " +
                               (currMemoryLeft - initialMemoryLeft)/oneMegabyte + "Mbs.");

        return currMemoryLeft;
    }


    // ---------- Integer only based bench --------------------
    private static final String INTEGER_BENCH = "benchFormatInteger";
    private static String benchFormatInteger(NumberFormat nf) {
        String str = "";
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++)
            str = nf.format((double) j);
        return str;
    }

    // This reproduces the throughput load added in benchFormatInteger
    static double integerThroughputLoad() {
        double d = 0.0d;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            d = (double) j;
        }
        return d;
    }

    // Runs integerThroughputLoad and calculate its mean load
    static void calculateIntegerThroughputLoad() {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = integerThroughputLoad();
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }


        if (Verbose)
            System.out.println(
               "calculated throughput load for " + INTEGER_BENCH +
               " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }

    // ---------- Fractional only based bench --------------------
    private static final String FRACTIONAL_BENCH = "benchFormatFractional";
    private static String benchFormatFractional(NumberFormat nf) {
        String str = "";
        double floatingN = 1.0d / (double) MAX_RANGE;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++)
            str = nf.format(floatingN * (double) j);
        return str;
    }

    // This reproduces the throughput load added in benchFormatFractional
    static double fractionalThroughputLoad() {
        double d = 0.0d;
        double floatingN = 1.0d / (double) MAX_RANGE;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            d = floatingN * (double) j;
        }
        return d;
    }

    // Runs fractionalThroughputLoad and calculate its mean load
    static void calculateFractionalThroughputLoad() {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = fractionalThroughputLoad();
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }

        if (Verbose)
        System.out.println(
            "calculated throughput load for " + FRACTIONAL_BENCH +
            " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }

    // ---------- An Small Integral bench --------------------
    //  that limits the magnitude of tested double values
    private static final String SMALL_INTEGRAL_BENCH = "benchFormatSmallIntegral";
    private static String benchFormatSmallIntegral(NumberFormat nf) {
        String str = "";
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++)
            str = nf.format(((double) j) / 1000.0d);
        return str;
    }

    // This reproduces the throughput load added in benchFormatSmallIntegral
    static double smallIntegralThroughputLoad() {
        double d = 0.0d;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            d = (double) j / 1000.0d;
        }
        return d;
    }

    // Runs small_integralThroughputLoad and calculate its mean load
    static void calculateSmallIntegralThroughputLoad() {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = smallIntegralThroughputLoad();
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }

        if (Verbose)
        System.out.println(
            "calculated throughput load for " + SMALL_INTEGRAL_BENCH +
            " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }

    // ---------- A fair and simple bench --------------------
    private static final String FAIR_SIMPLE_BENCH = "benchFormatFairSimple";
    private static String benchFormatFairSimple(NumberFormat nf, boolean isCurrency) {
        String str = "";
        double seed = isCurrency ?  0.0010203040506070809 : 0.00010203040506070809;
        double d = (double) -MAX_RANGE;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            d = d  + 1.0d + seed;
            str = nf.format(d);
        }
        return str;
    }

    // This reproduces the throughput load added in benchFormatFairSimple
    static double fairSimpleThroughputLoad() {
        double seed =  0.00010203040506070809;
        double delta = 0.0d;
        double d = (double) -MAX_RANGE;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            d = d + 1.0d + seed;
        }
        return d;
    }

    // Runs fairThroughputLoad and calculate its mean load
    static void calculateFairSimpleThroughputLoad() {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = fairSimpleThroughputLoad();
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }

        if (Verbose)
        System.out.println(
            "calculated throughput load for " + FAIR_SIMPLE_BENCH +
            " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }

    // ---------- Fractional part is only made of nines bench --------------
    private static final String FRACTIONAL_ALL_NINES_BENCH = "benchFormatFractionalAllNines";
    private static String benchFormatFractionalAllNines(NumberFormat nf, boolean isCurrency) {
        String str = "";
        double fractionalEven = isCurrency ?  0.993000001 : 0.99930000001;
        double fractionalOdd  = isCurrency ?  0.996000001 : 0.99960000001;
        double fractional;
        double d;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            if ((j & 1) == 0)
                fractional = fractionalEven;
            else
                fractional = fractionalOdd;
            if ( j >= 0)
                d = (double ) j + fractional;
            else d = (double) j - fractional;
            str = nf.format(d);
        }
        return str;
    }

    // This reproduces the throughput load added in benchFormatFractionalAllNines
    static double fractionalAllNinesThroughputLoad() {
        double fractionalEven = 0.99930000001;
        double fractionalOdd  = 0.99960000001;
        double fractional;
        double d = 0.0d;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            if ((j & 1) == 0)
                fractional = fractionalEven;
            else fractional = fractionalOdd;
            if ( j >= 0)
                d = (double ) j + fractional;
            else d = (double) j - fractional;
        }
        return d;
    }

    // Runs fractionalAllNinesThroughputLoad and calculate its mean load
    static void calculateFractionalAllNinesThroughputLoad() {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = fractionalAllNinesThroughputLoad();
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }

        if (Verbose)
            System.out.println(
               "calculated throughput load for " + FRACTIONAL_ALL_NINES_BENCH +
               " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }

    // ---------- Number is only made of nines bench --------------
    private static final String ALL_NINES_BENCH = "benchFormatAllNines";
    private static String benchFormatAllNines(NumberFormat nf, boolean isCurrency) {
        String str = "";
        double[] decimaAllNines =
            {9.9993, 99.9993, 999.9993, 9999.9993, 99999.9993,
             999999.9993, 9999999.9993, 99999999.9993, 999999999.9993};
        double[] currencyAllNines =
            {9.993, 99.993, 999.993, 9999.993, 99999.993,
             999999.993, 9999999.993, 99999999.993, 999999999.993};
        double[] valuesArray = (isCurrency) ? currencyAllNines : decimaAllNines;
        double seed = 1.0 / (double) MAX_RANGE;
        double d;
        int id;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            id = (j >=  0) ? j % 9 : -j % 9;
            if ((j & 1) == 0)
                d = valuesArray[id] + id * seed;
            else
                d = valuesArray[id] - id * seed;
            str = nf.format(d);
        }
        return str;
    }

    // This reproduces the throughput load added in benchFormatAllNines
    static double allNinesThroughputLoad() {
        double[] decimaAllNines =
            {9.9993, 99.9993, 999.9993, 9999.9993, 99999.9993,
             999999.9993, 9999999.9993, 99999999.9993, 999999999.9993};
        double[] valuesArray = decimaAllNines;
        double seed = 1.0 / (double) MAX_RANGE;
        double d = 0.0d;
        int id;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            id = (j >=  0) ? j % 9 : -j % 9;
            if ((j & 1) == 0)
                d = valuesArray[id] + id * seed;
            else
                d = valuesArray[id] - id * seed;
        }
        return d;
    }

    // Runs allNinesThroughputLoad and calculate its mean load
    static void calculateAllNinesThroughputLoad() {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = allNinesThroughputLoad();
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }

        if (Verbose)
            System.out.println(
               "calculated throughput load for " + ALL_NINES_BENCH +
               " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }



    // --- A fair bench trying (hopefully) to reproduce business applicatons  ---

    /*  benchFormatFair uses the following formula :
     *   y = F(x) = sign(x) * x**2 * ((1000/MAX_RANGE)**2).
     *
     *  which converts in the loop as (if j is the loop index) :
     *   x = double(j)
     *   k = 1000.0d * double(MAX_RANGE)
     *   y = sign(j) * x**2 * k**2
     *
     *  This is a flattened parabolic curve where only the j values
     *  in [-1000, 1000] will provide y results in [-1, +1] interval,
     *  and for abs(j) >= 1000 the result y will be greater than 1.
     *
     *  The difference with benchFormatSmallIntegral is that since y results
     *  follow a parabolic curve the magnitude of y grows much more rapidly
     *  and closer to j values when abs(j) >= 1000:
     *   - for |j| < 1000,  SmallIntegral(j) < 1.0 and fair(j) < 1.0
     *   - for j in [1000, 10000[
     *      SmallIntegral(j) is in [1, 10[
     *      Fair(j) is in [4, 400[
     *   - for j in [10000,100000[
     *      SmallIntegral(j) is in [10, 100[
     *      Fair(j) is in [400,40000[
     *   - for j in [100000,1000000[
     *      SmallIntegral(j) is in [100, 1000[
     *      Fair(j) is in [40000, 4000000[
     *
     *  Since double values for j less than 100000 provide only 4 digits in the
     *  integral, values greater than 250000 provide at least 6 digits, and 500000
     *  computes to 1000000, the distribution is roughly half with less than 5
     *  digits and half with at least 6 digits in the integral part.
     *
     *  Compared to FairSimple bench, this represents an application where 20% of
     *  the double values to format are less than 40000.0 absolute value.
     *
     *  Fair(j) is close to the magnitude of j when j > 100000 and is hopefully
     *  more representative of what may be found in general in business apps.
     *  (assumption : there will be mainly either small or large values, and
     *   less values in middle range).
     *
     *  We could get even more precise distribution of values using formula :
     *   y = sign(x) * abs(x)**n * ((1000 / MAX_RANGE)**n) where n > 2,
     *  or even well-known statistics function to fine target such distribution,
     *  but we have considred that the throughput load for calculating y would
     *  then be too high. We thus restrain the use of a power of 2 formula.
     */

    private static final String FAIR_BENCH = "benchFormatFair";
    private static String benchFormatFair(NumberFormat nf) {
        String str = "";
        double k = 1000.0d / (double) MAX_RANGE;
        k *= k;

        double d;
        double absj;
        double jPowerOf2;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            absj = (double) j;
            jPowerOf2 = absj * absj;
            d = k * jPowerOf2;
            if (j < 0) d = -d;
            str = nf.format(d);
        }
        return str;
    }

    // This is the exact throughput load added in benchFormatFair
    static double fairThroughputLoad() {
        double k = 1000.0d / (double) MAX_RANGE;
        k *= k;

        double d = 0.0d;
        double absj;
        double jPowerOf2;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            absj = (double) j;
            jPowerOf2 = absj * absj;
            d = k * jPowerOf2;
            if (j < 0) d = -d;
        }
        return d;
    }

    // Runs fairThroughputLoad and calculate its mean load
    static void calculateFairThroughputLoad() {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = fairThroughputLoad();
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }

        if (Verbose)
            System.out.println(
               "calculated throughput load for " + FAIR_BENCH +
               " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }

    // ---------- All double values are very close to a tie --------------------
    // i.e. like 123.1235 (for decimal case) or 123.125 (for currency case).

    private static final String TIE_BENCH = "benchFormatTie";
    private static String benchFormatTie(NumberFormat nf, boolean isCurrency) {
        double d;
        String str = "";
        double fractionaScaling = (isCurrency) ? 1000.0d : 10000.0d;
        int fixedFractionalPart = (isCurrency) ? 125 : 1235;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            d = (((double) j * fractionaScaling) +
                 (double) fixedFractionalPart) / fractionaScaling;
            str = nf.format(d);
        }
        return str;
    }

    // This is the exact throughput load added in benchFormatTie
    static double tieThroughputLoad(boolean isCurrency) {
        double d = 0.0d;
        double fractionaScaling = (isCurrency) ? 1000.0d : 10000.0d;
        int fixedFractionalPart = (isCurrency) ? 125 : 1235;
        for (int j = - MAX_RANGE; j <= MAX_RANGE; j++) {
            d = (((double) j * fractionaScaling) +
                 (double) fixedFractionalPart) / fractionaScaling;
        }
        return d;
    }

    // Runs tieThroughputLoad and calculate its mean load
    static void calculateTieThroughputLoad(boolean isCurrency) {
        int nbRuns = NB_RUNS;
        long elapsedTime = 0;
        double foo;

        for (int i = 1; i <= nbRuns; i++) {

            long startTime = System.nanoTime();
            foo = tieThroughputLoad(isCurrency);
            long estimatedTime = System.nanoTime() - startTime;
            if (i > 3) elapsedTime += estimatedTime / 1000;
        }

        if (Verbose)
            System.out.println(
               "calculated throughput load for " + TIE_BENCH +
               " bench is = " + (elapsedTime / (nbRuns - 3)) + " microseconds");
    }


    // Print statistics for passed times results of benchName.
    static void printPerfResults(long[] times, String benchName) {
        int nbBenches = times.length;

        long totalTimeSpent = 0;
        long meanTimeSpent;

        double variance = 0;
        double standardDeviation = 0;

        // Calculates mean spent time
        for (int i = 1; i <= nbBenches; i++)
            totalTimeSpent += times[i-1];
        meanTimeSpent = totalTimeSpent / nbBenches;

        // Calculates standard deviation
        for (int j = 1; j <= nbBenches; j++)
            variance += Math.pow(((double)times[j-1] - (double)meanTimeSpent), 2);
        variance = variance / (double) times.length;
        standardDeviation = Math.sqrt(variance) / meanTimeSpent;

        // Print result and statistics for benchName
        System.out.println(
           "Statistics (starting at 4th bench) for bench " + benchName +
           "\n for last " + nbBenches +
           " runs out of " + NB_RUNS +
           " , each with 2x" + MAX_RANGE + " format(double) calls : " +
           "\n  mean exec time = " + meanTimeSpent + " microseconds" +
           "\n  standard deviation = " + String.format("%.3f", standardDeviation) + "% \n");
    }

    public static void main(String[] args) {

        if (args.length >= 1) {
            // Parse args, just checks expected ones. Ignore others or dups.
            if (args[0].equals("-help")) {
                usage();
                return;
            }

            for (String s : args) {
                if (s.equals("-doit"))
                    DoIt = true;
                else if (s.equals("-verbose"))
                    Verbose = true;
            }
        } else {
            // No arguments, skips the benchmarks and exits.
            System.out.println(
                "Test skipped with success by default. See -help for details.");
            return;
        }

        if (!DoIt) {
            if (Verbose)
                usage();
            System.out.println(
                "Test skipped and considered successful.");
            return;
        }

        System.out.println("Single Threaded micro benchmark evaluating " +
                           "the throughput of java.text.DecimalFormat.format() call stack.\n");

        String fooString = "";

        // Run benches for decimal instance
        DecimalFormat df = (DecimalFormat) NumberFormat.getInstance(Locale.US);
        System.out.println("Running with a decimal instance of DecimalFormat.");

        calculateIntegerThroughputLoad();
        fooString =
            BenchType.INTEGER_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        calculateFractionalThroughputLoad();
        fooString =
            BenchType.FRACTIONAL_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        calculateSmallIntegralThroughputLoad();
        fooString =
            BenchType.SMALL_INTEGRAL_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        calculateFractionalAllNinesThroughputLoad();
        fooString =
            BenchType.FRACTIONAL_ALL_NINES_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        calculateAllNinesThroughputLoad();
        fooString =
            BenchType.ALL_NINES_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        calculateFairSimpleThroughputLoad();
        fooString =
            BenchType.FAIR_SIMPLE_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        calculateFairThroughputLoad();
        fooString =
            BenchType.FAIR_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        calculateTieThroughputLoad(false);
        fooString =
            BenchType.TIE_BENCH.runBenchAndPrintStatistics(NB_RUNS, df, false);

        // Run benches for currency instance
        DecimalFormat cf = (DecimalFormat) NumberFormat.getCurrencyInstance(Locale.US);
        System.out.println("Running with a currency instance of DecimalFormat.");

        calculateIntegerThroughputLoad();
        fooString =
            BenchType.INTEGER_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

        calculateFractionalThroughputLoad();
        fooString =
            BenchType.FRACTIONAL_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

        calculateSmallIntegralThroughputLoad();
        fooString =
            BenchType.SMALL_INTEGRAL_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

        calculateFractionalAllNinesThroughputLoad();
        fooString =
            BenchType.FRACTIONAL_ALL_NINES_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

        calculateAllNinesThroughputLoad();
        fooString =
            BenchType.ALL_NINES_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

        calculateFairSimpleThroughputLoad();
        fooString =
            BenchType.FAIR_SIMPLE_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

        calculateFairThroughputLoad();
        fooString =
            BenchType.FAIR_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

        calculateTieThroughputLoad(false);
        fooString =
            BenchType.TIE_BENCH.runBenchAndPrintStatistics(NB_RUNS, cf, false);

    }

    // This class to factorise what would be duplicated otherwise.
    static enum BenchType {

        INTEGER_BENCH("benchFormatInteger"),
        FRACTIONAL_BENCH("benchFormatFractional"),
        SMALL_INTEGRAL_BENCH("benchFormatSmallIntegral"),
        FAIR_SIMPLE_BENCH("benchFormatFairSimple"),
        FRACTIONAL_ALL_NINES_BENCH("benchFormatFractionalAllNines"),
        ALL_NINES_BENCH("benchFormatAllNines"),
        FAIR_BENCH("benchFormatFair"),
        TIE_BENCH("benchFormatTie");

        private final String name;

        BenchType(String name) {
            this.name = name;
        }

        String runBenchAndPrintStatistics(int nbRuns,
                         NumberFormat nf,
                         boolean isCurrency) {

            // We eliminate the first 3 runs in the time measurements
            // to let C2 do complete compilation and optimization work.
            long[] elapsedTimes = new long[nbRuns - 3];

            System.out.println("Now running " + nbRuns + " times bench " + name);

            String str = "";
            for (int i = 1; i <= nbRuns; i++) {

                stabilizeMemory(false);
                long startTime = System.nanoTime();

                switch(this) {
                case INTEGER_BENCH :
                    str = benchFormatInteger(nf);
                    break;
                case FRACTIONAL_BENCH :
                    str = benchFormatFractional(nf);
                    break;
                case SMALL_INTEGRAL_BENCH :
                    str = benchFormatSmallIntegral(nf);
                    break;
                case FRACTIONAL_ALL_NINES_BENCH :
                    str = benchFormatFractionalAllNines(nf, isCurrency);
                    break;
                case ALL_NINES_BENCH :
                    str = benchFormatAllNines(nf, isCurrency);
                    break;
                case FAIR_SIMPLE_BENCH :
                    str = benchFormatFairSimple(nf, isCurrency);
                    break;
                case FAIR_BENCH :
                    str = benchFormatFair(nf);
                    break;
                case TIE_BENCH :
                    str = benchFormatTie(nf, isCurrency);
                    break;

                default:
                }


                long estimatedTime = System.nanoTime() - startTime;
                if (i > 3)
                    elapsedTimes[i-4] = estimatedTime / 1000;

                if (Verbose)
                    System.out.println(
                                       "calculated time for " + name +
                                       " bench " + i + " is = " +
                                       (estimatedTime / 1000) + " microseconds");
                else System.out.print(".");

                stabilizeMemory(true);
            }

            System.out.println(name + " Done.");

            printPerfResults(elapsedTimes, name);

            return str;
        }
    }

}