/* __ __ __ __ __ ___ * \ \ / / \ \ / / __/ * \ \/ / /\ \ \/ / / * \____/__/ \__\____/__/.ɪᴏ * ᶜᵒᵖʸʳᶦᵍʰᵗ ᵇʸ ᵛᵃᵛʳ ⁻ ˡᶦᶜᵉⁿˢᵉᵈ ᵘⁿᵈᵉʳ ᵗʰᵉ ᵃᵖᵃᶜʰᵉ ˡᶦᶜᵉⁿˢᵉ ᵛᵉʳˢᶦᵒⁿ ᵗʷᵒ ᵈᵒᵗ ᶻᵉʳᵒ */ package io.vavr; import io.vavr.collection.*; import io.vavr.control.LazyBenchmark; import io.vavr.idiom.ForBenchmark; import io.vavr.idiom.PatternMatchingBenchmark; import io.vavr.idiom.TryBenchmark; import io.vavr.idiom.TupleBenchmark; import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.results.RunResult; import org.openjdk.jmh.runner.Runner; import org.openjdk.jmh.runner.RunnerException; import org.openjdk.jmh.runner.options.ChainedOptionsBuilder; import org.openjdk.jmh.runner.options.OptionsBuilder; import org.openjdk.jmh.runner.options.TimeValue; import org.openjdk.jmh.runner.options.VerboseMode; import java.util.Collection; import java.util.Random; import java.util.concurrent.TimeUnit; import static io.vavr.API.Array; public class JmhRunner { /** * Runs all the available benchmarks in precision mode. * Note: it takes about 3 hours. */ public static void main(String[] args) { final Array<Class<?>> CLASSES = Array( ArrayBenchmark.class, BitSetBenchmark.class, CharSeqBenchmark.class, HashSetBenchmark.class, ListBenchmark.class, PriorityQueueBenchmark.class, VectorBenchmark.class, LazyBenchmark.class, ForBenchmark.class, PatternMatchingBenchmark.class, TryBenchmark.class, TupleBenchmark.class ); runDebugWithAsserts(CLASSES); runSlowNoAsserts(CLASSES); } public enum Includes { JAVA("java"), FUNCTIONAL_JAVA("fjava"), PCOLLECTIONS("pcollections"), ECOLLECTIONS("ecollections"), CLOJURE("clojure"), SCALAZ("scalaz"), SCALA("scala"), VAVR("vavr"); private final String name; Includes(String name) { this.name = name; } @Override public String toString() { return name; } } /** enables debugging and assertions for benchmarks and production code - the speed results will be totally unreliable */ public static void runDebugWithAsserts(Array<Class<?>> groups, Includes... includes) { run(0, 1, 1, ForkJvm.DISABLE, VerboseMode.SILENT, Assertions.ENABLE, PrintInlining.DISABLE, groups, includes); MemoryUsage.printAndReset(); } @SuppressWarnings("unused") public static void runQuickNoAsserts(Array<Class<?>> groups, Includes... includes) { run(5, 5, 10, ForkJvm.ENABLE, VerboseMode.NORMAL, Assertions.DISABLE, PrintInlining.DISABLE, groups, includes).print(); } @SuppressWarnings("unused") public static void runNormalNoAsserts(Array<Class<?>> groups, Includes... includes) { run(7, 7, 300, ForkJvm.ENABLE, VerboseMode.NORMAL, Assertions.DISABLE, PrintInlining.DISABLE, groups, includes).print(); } @SuppressWarnings("unused") public static void runSlowNoAsserts(Array<Class<?>> groups, Includes... includes) { run(15, 15, 400, ForkJvm.ENABLE, VerboseMode.EXTRA, Assertions.DISABLE, PrintInlining.DISABLE, groups, includes).print(); } private static BenchmarkPerformanceReporter run(int warmupIterations, int measurementIterations, int millis, ForkJvm forkJvm, VerboseMode silent, Assertions assertions, PrintInlining printInlining, Array<Class<?>> groups, Includes[] includes) { final Array<String> includeNames = Array.of(includes.length == 0 ? Includes.values() : includes).map(Includes::toString); final Array<String> classNames = groups.map(Class::getCanonicalName); final Array<RunResult> results = run(warmupIterations, measurementIterations, millis, forkJvm, silent, assertions, printInlining, classNames, includeNames); return BenchmarkPerformanceReporter.of(includeNames, classNames, results); } private static Array<RunResult> run(int warmupIterations, int measurementIterations, int millis, ForkJvm forkJvm, VerboseMode verboseMode, Assertions assertions, PrintInlining printInlining, Array<String> classNames, Array<String> includeNames) { try { final ChainedOptionsBuilder builder = new OptionsBuilder() .shouldDoGC(true) .verbosity(verboseMode) .shouldFailOnError(true) .mode(Mode.Throughput) .timeUnit(TimeUnit.SECONDS) .warmupTime(TimeValue.milliseconds(millis)) .warmupIterations(warmupIterations) .measurementTime(TimeValue.milliseconds(millis)) .measurementIterations(measurementIterations) .forks(forkJvm.forkCount) /* We are using 4Gb and setting NewGen to 100% to avoid GC during testing. Any GC during testing will destroy the iteration (i.e. introduce unreliable noise in the measurement), which should get ignored as an outlier */ .jvmArgsAppend("-XX:+UseG1GC", "-Xss100m", "-Xms4g", "-Xmx4g", "-XX:MaxGCPauseMillis=1000", "-XX:+UnlockExperimentalVMOptions", "-XX:G1NewSizePercent=100", "-XX:G1MaxNewSizePercent=100", assertions.vmArg); final String includePattern = includeNames.mkString("\\..*?\\b(", "|", ")_"); classNames.forEach(name -> builder.include(name + includePattern)); if (printInlining == PrintInlining.ENABLE) { builder.jvmArgsAppend("-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintInlining"); /* might help in deciding when the JVM is properly warmed up - or where to optimize the code */ } return Array.ofAll(new Runner(builder.build()).run()); } catch (RunnerException e) { throw new RuntimeException(e); } } /* Options */ private enum ForkJvm { ENABLE(1), DISABLE(0); final int forkCount; ForkJvm(int forkCount) { this.forkCount = forkCount; } } private enum Assertions { ENABLE("-enableassertions"), DISABLE("-disableassertions"); final String vmArg; Assertions(String vmArg) { this.vmArg = vmArg; } } private enum PrintInlining { ENABLE, DISABLE; } /* Helper methods */ public static Integer[] getRandomValues(int size, int seed) { return getRandomValues(size, seed, false); } public static Integer[] getRandomValues(int size, int seed, boolean nonNegative) { return getRandomValues(size, nonNegative, new Random(seed)); } public static Integer[] getRandomValues(int size, boolean nonNegative, Random random) { final Integer[] results = new Integer[size]; for (int i = 0; i < size; i++) { final int value = random.nextInt(size) - (nonNegative ? 0 : (size / 2)); results[i] = value; } return results; } /** Randomly mutate array positions */ public static <T> int[] shuffle(int[] array, Random random) { for (int i = array.length; i > 1; i--) { swap(array, i - 1, random.nextInt(i)); } return array; } static <T> void swap(int[] array, int i, int j) { final int temp = array[i]; array[i] = array[j]; array[j] = temp; } /** used for dead code elimination and correctness assertion inside the benchmarks */ public static int aggregate(int x, int y) { return x ^ y; } /** simplifies construction of immutable collections - with assertion and memory benchmarking */ public static <T extends Collection<?>, R> R create(Function1<T, R> function, T source, Function1<R, Boolean> assertion) { return create(function, source, source.size(), assertion); } @SuppressWarnings("unchecked") public static <T, R> R create(Function1<T, R> function, T source, int elementCount, Function1<R, Boolean> assertion) { final R result = function.apply(source); assert assertion.apply(result); MemoryUsage.storeMemoryUsages(elementCount, result); return result; } }