view src/main/java/org/openjdk/gcbench/GCBench.java @ 91:e8e80f26edfb

Sanity test for checking non-allocating performance.
author shade
date Thu, 23 Nov 2017 15:23:31 +0100
parents 583fef4276f5
children
line wrap: on
line source

/*
 * Copyright (c) 2017, Red Hat Inc. 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * 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.
 */
package org.openjdk.gcbench;

import joptsimple.OptionParser;
import joptsimple.OptionSet;
import joptsimple.OptionSpec;
import org.openjdk.gcbench.tests.*;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.*;
import org.openjdk.jmh.util.Utils;
import org.openjdk.jmh.util.Version;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;

public class GCBench {

    private final PrintWriter pw;
    private Options baseOpts;
    private String testFilter;
    private List<Test> tests;

    public static void main(String... args) throws RunnerException, IOException {
        try {
            GCBench bench = new GCBench(args);
            bench.run();
        } catch (HelpPrintException e) {
            // do nothing
        }
    }

    public GCBench(String[] args) throws RunnerException, IOException, HelpPrintException {
        pw = new PrintWriter(System.out, true);

        pw.println("GC Benchmarks Suite");
        pw.println("----------------------------------------------------------------------------------------------------------");
        pw.println();

        pw.println("# " + Version.getVersion());
        pw.println("# " + Utils.getCurrentJvmVersion());
        pw.println("# " + Utils.getCurrentOSVersion());
        pw.println();

        Utils.reflow(pw,
                "These tests assess the garbage collector performance. These tests implicitly test other aspects of system " +
                        "under test, including hardware, OS and JVM tuning. Some tests may implicitly test the testing infrastructure " +
                        "itself, although the benchmark code tries to reduce the infrastructural impact as much as possible.",
                80, 2);
        pw.println();

        Utils.reflow(pw,
                "If you are sharing this report, please share it in full, including the JVM version, OS flavor and version, " +
                        "plus some data on used hardware.",
                80, 2);

        pw.println();

        pw.println("  Use -h to get help on available options.");
        pw.println();

        OptionParser parser = new OptionParser();
        parser.formatHelpWith(new OptionFormatter());

        OptionSpec<String> optTests = parser.accepts("t", "Test names.")
                .withRequiredArg().ofType(String.class).describedAs("regexp")
                .defaultsTo(".*");

        OptionSpec<Mode> optMode = parser.accepts("m", "Running mode, one of " + Arrays.toString(Mode.values()) + ".")
                .withRequiredArg().ofType(Mode.class).describedAs("mode")
                .defaultsTo(Mode.normal);

        OptionSpec<Integer> optMaxHeap = parser.accepts("maxHeap", "Max heap to be used for tests. Leave unset to enable auto-detection")
                .withRequiredArg().ofType(Integer.class).describedAs("MB");

        OptionSpec<Integer> optMinHeap = parser.accepts("minHeap", "Min heap to be used for tests.")
                .withRequiredArg().ofType(Integer.class).describedAs("MB");

        OptionSpec<Integer> optMinThreads = parser.accepts("minThreads", "Min threads to be used for tests.")
                .withRequiredArg().ofType(Integer.class).describedAs("#").defaultsTo(1);

        OptionSpec<Integer> optMaxThreads = parser.accepts("maxThreads", "Max threads to be used for tests. Leave unset to enable auto-detection.")
                .withRequiredArg().ofType(Integer.class).describedAs("#");

        parser.accepts("h", "Print help.");

        OptionSet set = parser.parse(args);
        if (set.has("h")) {
            parser.printHelpOn(System.out);
            throw new HelpPrintException();
        }

        final int MIN_THREADS = set.valueOf(optMinThreads);
        final int MAX_THREADS;
        if (set.has(optMaxThreads)) {
            MAX_THREADS = optMaxThreads.value(set);
        } else {
            MAX_THREADS = Runtime.getRuntime().availableProcessors()*2;
        }

        List<Integer> threads = new ArrayList<>();
        threads.add(1);
        threads.add(Runtime.getRuntime().availableProcessors()/2);
        threads.add(Runtime.getRuntime().availableProcessors());
        threads.add(Runtime.getRuntime().availableProcessors()*2);
        threads.removeIf(i -> i < MIN_THREADS || i > MAX_THREADS);

        int minHeap;
        if (set.has(optMinHeap)) {
            minHeap = set.valueOf(optMinHeap);
        } else {
            minHeap = Runtime.getRuntime().availableProcessors() * 1024; // 1 GB per thread
        }

        if (set.has(optMaxHeap)) {
            HeapSizeManager.override(minHeap, set.valueOf(optMaxHeap));
        } else {
            pw.println("===== Calibrating max heap size");
            pw.println();
            HeapSizeManager.init(minHeap, pw);
        }

        Options opts = new OptionsBuilder()
                .detectJvmArgs()
                .threads(Threads.MAX)
                .verbosity(VerboseMode.SILENT)
                .shouldFailOnError(true)
                .build();

        int targetAllocRate_MbPerSec = 3000;

        switch (set.valueOf(optMode)) {
            case flash:
                opts = new OptionsBuilder()
                        .parent(opts)
                        .warmupIterations(3)
                        .warmupTime(TimeValue.seconds(1))
                        .measurementIterations(1)
                        .measurementTime(TimeValue.milliseconds(300))
                        .forks(1)
                        .build();
                break;
            case quick:
                opts = new OptionsBuilder()
                        .parent(opts)
                        .warmupIterations(3)
                        .warmupTime(TimeValue.seconds(1))
                        .measurementIterations(1)
                        .measurementTime(TimeValue.milliseconds(Math.max(1000, (HeapSizeManager.MAX_HEAP * 1000 / targetAllocRate_MbPerSec))))
                        .forks(1)
                        .build();
                break;
            case normal:
                opts = new OptionsBuilder()
                        .parent(opts)
                        .warmupIterations(3)
                        .warmupTime(TimeValue.seconds(1))
                        .measurementIterations(1)
                        .measurementTime(TimeValue.milliseconds(Math.max(1000, 3*(HeapSizeManager.MAX_HEAP * 1000 / targetAllocRate_MbPerSec))))
                        .forks(3)
                        .build();
                break;
            case tough:
                opts = new OptionsBuilder()
                        .parent(opts)
                        .warmupIterations(3)
                        .warmupTime(TimeValue.seconds(1))
                        .measurementIterations(1)
                        .measurementTime(TimeValue.seconds(Math.max(1000, 10*(HeapSizeManager.MAX_HEAP * 1000 / targetAllocRate_MbPerSec))))
                        .forks(5)
                        .build();
                break;
            default:
                throw new IllegalStateException();
        }

        baseOpts = opts;
        testFilter = set.valueOf(optTests);

        tests = new ArrayList<>();

        // alloc family
        {
            String groupDescr = "This tests no other activity happens except for the test itself. " +
                    "Runs the active non-allocating test.";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.sanity.DoNothing.class,
                    "sanity", groupDescr,
                    Dimensions.threads(Sequence.predefined(threads)),
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP))
            ));
        }

        // alloc family
        {
            String groupDescr = "Allocates the objects in almost completely empty heap as fast as it can. " +
                    "This tests what allocation pressure can the collector withstand without doing anything else. ";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.alloc.plain.Objects.class,
                    "alloc.peak.object", groupDescr + "Allocates plain Java Objects.",
                    Dimensions.threads(Sequence.predefined(threads)),
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.alloc.plain.PrimArray.class,
                    "alloc.peak.intarray", groupDescr + "Allocates int[] arrays of different sizes.",
                    Dimensions.threads(Sequence.predefined(threads)),
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen(1, 10_000_000))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.alloc.plain.RefArray.class,
                    "alloc.peak.refarray", groupDescr + "Allocates Object[] arrays of different sizes.",
                    Dimensions.threads(Sequence.predefined(threads)),
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen(1, 10_000_000))
            ));
        }

        {
            String groupDescr = "Allocates the uninitialized arrays in almost completely empty heap as fast as it can. " +
                    "This is the torturous test that allocates more objects than any pure Java application can do, and " +
                    "stresses the allocation paths in collector. ";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.alloc.uninit.IntArray.class,
                    "alloc.uninit.intarray", groupDescr + "Allocates uninitialized int[] arrays of different sizes.",
                    Dimensions.threads(Sequence.predefined(threads)),
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen(1, 10_000))
            ));
        }

        if (false) { // TODO: Enable back once they are important again
            String groupDescr = "Allocates the objects in almost completely empty heap, with rate limiting. " +
                    "If the collector needs some headroom to do cleanup, rate limited tests would show how much " +
                    "headroom is needed. ";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.alloc.ratelimited.Objects.class,
                    "alloc.rated.object", groupDescr + "Allocates plain Java Objects.",
                    true,
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.alloc.ratelimited.PrimArray.class,
                    "alloc.rated.intarray", groupDescr + "Allocates int[] arrays of different sizes.",
                    true,
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen(1, 10_000_000))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.alloc.ratelimited.RefArray.class,
                    "alloc.rated.refarray", groupDescr + "Allocates Object[] arrays of different sizes.",
                    true,
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen(1, 10_000_000))
            ));
        }

        {
            String groupDesc = "Allocates the objects in heap, when heap is not empty. This tests how well the collector " +
                    "can withstand the allocation pressure when there is other potential work to do. ";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.retain.RefArray.class,
                    "retain.array", groupDesc + "Retains a reference array of given size.",
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(100_000, 100_000_000))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.retain.LinkedLists.class,
                    "retain.linkedlist", groupDesc + "Retains a linked list of given size.",
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(100_000, 100_000_000))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.retain.Tree.class,
                    "retain.tree", groupDesc + "Retains a tree of given size.",
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(100_000, 100_000_000))
            ));


            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.retain.Tree.class,
                    "retain.hashmap", groupDesc + "Retains a HashMap of given size.",
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(100_000, 100_000_000))
            ));
        }

        {
            String groupDescr = "Populates heap with data, and randomly overwrites the part of it to cause fragmentation. " +
                    "The tests are rate-limited to see on what load the collector breaks down. ";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.fragger.ArrayFragger.class,
                    "fragger.array", groupDescr + "Retains a single large reference array.",
                    true,
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.lds(4)
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.fragger.TreeFragger.class,
                    "fragger.tree", groupDescr + "Retains a binary tree of Nodes.",
                    true,
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.lds(4)
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.fragger.LinkedListFragger.class,
                    "fragger.linkedlist", groupDescr + "Retains a LinkedList.",
                    true,
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.lds(4)
            ));
        }

        {
            String groupDescr = "Does the allocations, but retains each ${size}-th allocated object. ";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.sieve.Objects.class,
                    "sieve.object", groupDescr + "Allocates Java Objects.",
                    Dimensions.heapSize(Sequence.powersOfTwo_WithMax(HeapSizeManager.MIN_HEAP, HeapSizeManager.MAX_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(1, 10_000))
            ));
        }

        {
            String groupDescr = "Stresses the application root set. Beefs up the particular part of root set, " +
                    "and then runs peak allocation tests to see if it affects garbage collection. ";

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.roots.Strings.class,
                    "roots.strings", groupDescr + "Allocates and retains a number of interned Strings.",
                    Dimensions.heapSize(Sequence.predefined(HeapSizeManager.MIN_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(100, 100000))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.roots.Synchronizers.class,
                    "roots.synchronizers", groupDescr + "Inflates and retains a number of synchronized objects per each thread.",
                    Dimensions.heapSize(Sequence.predefined(HeapSizeManager.MIN_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(100, 100000))
            ));

            tests.add(new DimensionalTest(baseOpts, org.openjdk.gcbench.roots.Locals.class,
                    "roots.locals", groupDescr + "Produces lots of intermediate local variables on thread stacks.",
                    Dimensions.heapSize(Sequence.predefined(HeapSizeManager.MIN_HEAP)),
                    Dimensions.size(Sequence.powersOfTen_Sub(100, 100000))
            ));
        }

        {
            String groupDescr = "Estimates the runtime overhead for reads. These costs include all the infrastructural " +
                    "overheads too, and therefore the score differences matter, not their absolute values. This test " +
                    "is single-threaded. ";

            tests.add(new MachineCntTest(baseOpts, org.openjdk.gcbench.runtime.reads.Plain.class,
                    "runtime.reads.plain", groupDescr + "Reads plain Java fields.",
                    Dimensions.jvmMode(),
                    Dimensions.javaType()
            ));

            tests.add(new MachineCntTest(baseOpts, org.openjdk.gcbench.runtime.reads.Volatile.class,
                    "runtime.reads.volatile", groupDescr + "Reads volatile Java fields.",
                    Dimensions.jvmMode(),
                    Dimensions.javaType()
            ));
        }

        {
            String groupDescr = "Estimates the runtime overhead for reads. These costs include all the infrastructural " +
                    "overheads too, and therefore the score differences matter, not their absolute values. This test " +
                    "is single-threaded. ";

            tests.add(new MachineCntTest(baseOpts, org.openjdk.gcbench.runtime.writes.Plain.class,
                    "runtime.writes.plain", groupDescr + "Writes plain Java fields.",
                    Dimensions.jvmMode(),
                    Dimensions.javaType()
            ));

            tests.add(new MachineCntTest(baseOpts, org.openjdk.gcbench.runtime.writes.Volatile.class,
                    "runtime.writes.volatile", groupDescr + "Writes volatile Java fields.",
                    Dimensions.jvmMode(),
                    Dimensions.javaType()
            ));
        }
    }


    public enum Mode {
        flash,
        quick,
        normal,
        tough,
    }

    public void run() throws RunnerException {
        pw.println("  Available tests: ");
        for (Test test : tests) {
            pw.printf("    %-20s %n", test.label());
        }
        pw.println();

        Pattern pattern = Pattern.compile(testFilter);
        List<Test> runTests = new ArrayList<>();

        pw.println("  Matching tests:");
        for (Test test : tests) {
            if (test.label().equals("sanity") || pattern.matcher(test.label()).find()) {
                pw.printf("    %-20s%n", test.label());
                runTests.add(test);
            }
        }
        pw.println();

        for (Test test : runTests) {
            test.run();
        }
    }

    private class HelpPrintException extends Throwable {
    }
}