From 21c901b495f28806f238e3936b0a9900f939acff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pap=20L=C5=91rinc?= Date: Tue, 24 May 2016 02:22:48 +0300 Subject: [PATCH 1/2] Unified benchmarks --- .../BenchmarkPerformanceReporter.java | 16 ++-- .../java/javaslang/benchmark/JmhRunner.java | 67 +++++++++----- .../benchmark/collection/ArrayBenchmark.java | 32 ++----- .../benchmark/collection/ListBenchmark.java | 87 ++++++++++++------- .../collection/PriorityQueueBenchmark.java | 35 ++------ 5 files changed, 127 insertions(+), 110 deletions(-) diff --git a/javaslang-benchmark/src/test/java/javaslang/benchmark/BenchmarkPerformanceReporter.java b/javaslang-benchmark/src/test/java/javaslang/benchmark/BenchmarkPerformanceReporter.java index 8f65518983..3920729b50 100644 --- a/javaslang-benchmark/src/test/java/javaslang/benchmark/BenchmarkPerformanceReporter.java +++ b/javaslang-benchmark/src/test/java/javaslang/benchmark/BenchmarkPerformanceReporter.java @@ -224,11 +224,11 @@ private String calculateRatioStr(TestExecution baseResult, Option if (!alternativeResult.isDefined()) { return ""; } - double alternativeScore = alternativeResult.get().getScore(); + final double alternativeScore = alternativeResult.get().getScore(); if (alternativeScore == 0.0) { return ""; } - double ratio = baseResult.getScore() / alternativeScore; + final double ratio = baseResult.getScore() / alternativeScore; return ratio == 1.0 ? "" : PERFORMANCE_FORMAT.format(ratio) + "x"; } } @@ -390,14 +390,14 @@ public static TestExecution of(BenchmarkResult benchmarkResult, double outlierLo } public TestExecution(BenchmarkResult benchmark) { - Result primaryResult = benchmark.getPrimaryResult(); + final Result primaryResult = benchmark.getPrimaryResult(); fullName = benchmark.getParams().getBenchmark(); target = extractPart(fullName, 2); operation = extractPart(fullName, 1); implementation = extractPart(fullName, 0); paramKey = getParameterKey(benchmark); - ListStatistics statistics = createStatisticsWithoutOutliers(benchmark, outlierLowPct, outlierHighPct); + final ListStatistics statistics = createStatisticsWithoutOutliers(benchmark, outlierLowPct, outlierHighPct); sampleCount = statistics.getN(); score = statistics.getMean(); scoreError = statistics.getMeanErrorAt(0.999); @@ -409,15 +409,15 @@ private ListStatistics createStatisticsWithoutOutliers(BenchmarkResult benchmark .map(r -> r.getPrimaryResult().getScore()) .sorted() .collect(Vector.collector()); - int size = results.size(); - int outliersLow = (int) (size * outlierLowPct); - int outliersHigh = (int) (size * outlierHighPct); + final int size = results.size(); + final int outliersLow = (int) (size * outlierLowPct); + final int outliersHigh = (int) (size * outlierHighPct); results = results.drop(outliersLow).dropRight(outliersHigh); return new ListStatistics(results.toJavaList().stream().mapToDouble(r -> r).toArray()); } private String getParameterKey(BenchmarkResult benchmarkResult) { - BenchmarkParams params = benchmarkResult.getParams(); + final BenchmarkParams params = benchmarkResult.getParams(); return params.getParamsKeys().stream().map(params::getParam).collect(Collectors.joining(",")); } diff --git a/javaslang-benchmark/src/test/java/javaslang/benchmark/JmhRunner.java b/javaslang-benchmark/src/test/java/javaslang/benchmark/JmhRunner.java index e0b712f54e..6322c5106f 100644 --- a/javaslang-benchmark/src/test/java/javaslang/benchmark/JmhRunner.java +++ b/javaslang-benchmark/src/test/java/javaslang/benchmark/JmhRunner.java @@ -3,70 +3,95 @@ import org.openjdk.jmh.annotations.Mode; import org.openjdk.jmh.results.RunResult; import org.openjdk.jmh.runner.Runner; -import org.openjdk.jmh.runner.options.Options; -import org.openjdk.jmh.runner.options.OptionsBuilder; -import org.openjdk.jmh.runner.options.TimeValue; +import org.openjdk.jmh.runner.options.*; -import java.util.Collection; +import java.util.*; import java.util.concurrent.TimeUnit; public class JmhRunner { - private static final int WARMUP_ITERATIONS = 10; - private static final int MEASUREMENT_ITERATIONS = 40; + private static final int WARMUP_ITERATIONS = 20; + private static final int MEASUREMENT_ITERATIONS = 30; - private static final int QUICK_WARMUP_ITERATIONS = 5; + private static final int QUICK_WARMUP_ITERATIONS = 10; private static final int QUICK_MEASUREMENT_ITERATIONS = 10; public static void run(Class benchmarkClass) { - runAndReport(benchmarkClass, WARMUP_ITERATIONS, MEASUREMENT_ITERATIONS, Assertions.Disable); + runAndReport(benchmarkClass, WARMUP_ITERATIONS, MEASUREMENT_ITERATIONS, 500, PrintGc.Enable, Assertions.Disable); } public static void devRun(Class benchmarkClass) { - runAndReport(benchmarkClass, QUICK_WARMUP_ITERATIONS, QUICK_MEASUREMENT_ITERATIONS, Assertions.Disable); + runAndReport(benchmarkClass, QUICK_WARMUP_ITERATIONS, QUICK_MEASUREMENT_ITERATIONS, 200, PrintGc.Disable, Assertions.Disable); } public static void devRunWithAssertions(Class benchmarkClass) { - runAndReport(benchmarkClass, QUICK_WARMUP_ITERATIONS, QUICK_MEASUREMENT_ITERATIONS, Assertions.Enable); + runAndReport(benchmarkClass, QUICK_WARMUP_ITERATIONS, QUICK_MEASUREMENT_ITERATIONS, 200, PrintGc.Disable, Assertions.Enable); } - private static void runAndReport(Class benchmarkClass, int warmupIterations, int measurementIterations, Assertions assertions) { - Collection results = run(benchmarkClass, warmupIterations, measurementIterations, assertions); + private static void runAndReport(Class benchmarkClass, int warmupIterations, int measurementIterations, int millis, PrintGc printGc, Assertions assertions) { + final Collection results = run(benchmarkClass, warmupIterations, measurementIterations, millis, printGc, assertions); BenchmarkPerformanceReporter.of(results).print(); } - private static Collection run(Class benchmarkClass, int warmupIterations, int measurementIterations, Assertions assertions) { + private static Collection run(Class benchmarkClass, int warmupIterations, int measurementIterations, int millis, PrintGc printGc, Assertions assertions) { final Options opts = new OptionsBuilder() .include(benchmarkClass.getSimpleName()) .shouldDoGC(true) .shouldFailOnError(true) .mode(Mode.Throughput) .timeUnit(TimeUnit.SECONDS) - .warmupTime(TimeValue.milliseconds(500)) + .warmupTime(TimeValue.milliseconds(millis)) .warmupIterations(warmupIterations) - .measurementTime(TimeValue.milliseconds(500)) + .measurementTime(TimeValue.milliseconds(millis)) .measurementIterations(measurementIterations) .forks(1) // We are using 4Gb and setting NewGen to 100% to avoid GC during testing. // Any GC during testing will destroy the iteration, which should get ignored as an outlier - .jvmArgsAppend("-XX:+UseG1GC", "-Xss100m", "-Xms4g", "-Xmx4g", "-XX:+PrintGC", "-XX:MaxGCPauseMillis=1000", "-XX:+UnlockExperimentalVMOptions", "-XX:G1NewSizePercent=100", "-XX:G1MaxNewSizePercent=100", assertions.vmArg) + .jvmArgsAppend("-XX:+UseG1GC", "-Xss100m", "-Xms4g", "-Xmx4g", "-XX:MaxGCPauseMillis=1000", "-XX:+UnlockExperimentalVMOptions", "-XX:G1NewSizePercent=100", "-XX:G1MaxNewSizePercent=100", printGc.vmArg, assertions.vmArg) .build(); try { return new Runner(opts).run(); - } - catch (Exception e) { + } catch (Exception e) { throw new RuntimeException(e.getMessage(), e); } } - private static enum Assertions { + private enum Assertions { Enable("-enableassertions"), - Disable("-disableassertions") - ; + Disable("-disableassertions"); final String vmArg; + Assertions(String vmArg) { this.vmArg = vmArg; } } + + private enum PrintGc { + Enable("-XX:+PrintGC"), + Disable("-XX:-PrintGC"); + + final String vmArg; + + PrintGc(String vmArg) { + this.vmArg = vmArg; + } + } + + public static void assertEquals(T a, T b) { + if (!Objects.equals(a, b)) { + throw new IllegalStateException(a + " != " + b); + } + } + + public static Integer[] getRandomValues(int size, int seed) { + final Random random = new Random(seed); + + final Integer[] results = new Integer[size]; + for (int i = 0; i < size; i++) { + final int value = random.nextInt(size) - (size / 2); + results[i] = value; + } + return results; + } } diff --git a/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ArrayBenchmark.java b/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ArrayBenchmark.java index 23179ea2f7..f87bc3b15e 100644 --- a/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ArrayBenchmark.java +++ b/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ArrayBenchmark.java @@ -1,45 +1,25 @@ package javaslang.benchmark.collection; -import javaslang.benchmark.JmhRunner; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Level; -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.TearDown; - -import java.util.Objects; -import java.util.Random; +import org.openjdk.jmh.annotations.*; + +import static javaslang.benchmark.JmhRunner.*; public class ArrayBenchmark { public static void main(String... args) { /* main is more reliable than a test */ - JmhRunner.run(ArrayBenchmark.class); + run(ArrayBenchmark.class); } @State(Scope.Benchmark) public static class Base { - @Param({ "10", "100", "1000", "10000"}) + @Param({ "10", "100", "1000", "10000" }) public int CONTAINER_SIZE; public Integer[] ELEMENTS; @Setup public void setup() { - final Random random = new Random(0); - - ELEMENTS = new Integer[CONTAINER_SIZE]; - for (int i = 0; i < CONTAINER_SIZE; i++) { - final int value = random.nextInt(CONTAINER_SIZE) - (CONTAINER_SIZE / 2); - ELEMENTS[i] = value; - } - } - - protected static void assertEquals(T a, T b) { - if (!Objects.equals(a, b)) { - throw new IllegalStateException(a + " != " + b); - } + ELEMENTS = getRandomValues(CONTAINER_SIZE, 0); } } diff --git a/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ListBenchmark.java b/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ListBenchmark.java index 19568a33fd..6216802b19 100644 --- a/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ListBenchmark.java +++ b/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/ListBenchmark.java @@ -1,47 +1,28 @@ package javaslang.benchmark.collection; -import javaslang.benchmark.JmhRunner; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Level; -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.TearDown; - -import java.util.Collections; -import java.util.Iterator; -import java.util.Objects; -import java.util.Random; +import org.openjdk.jmh.annotations.*; +import scala.compat.java8.JFunction; -public class ListBenchmark { +import java.util.*; +import java.util.stream.Collectors; + +import static javaslang.benchmark.JmhRunner.*; +public class ListBenchmark { public static void main(String... args) { /* main is more reliable than a test */ - JmhRunner.run(ListBenchmark.class); + run(ListBenchmark.class); } @State(Scope.Benchmark) public static class Base { - @Param({ "10", "100", "1000", "10000"}) + @Param({ "10", "100", "1000" }) public int CONTAINER_SIZE; public Integer[] ELEMENTS; @Setup public void setup() { - final Random random = new Random(0); - - ELEMENTS = new Integer[CONTAINER_SIZE]; - for (int i = 0; i < CONTAINER_SIZE; i++) { - final int value = random.nextInt(CONTAINER_SIZE) - (CONTAINER_SIZE / 2); - ELEMENTS[i] = value; - } - } - - protected static void assertEquals(T a, T b) { - if (!Objects.equals(a, b)) { - throw new IllegalStateException(a + " != " + b); - } + ELEMENTS = getRandomValues(CONTAINER_SIZE, 0); } } @@ -240,4 +221,52 @@ public void slang_persistent(Initialized state) { } } + public static class GroupBy extends Base { + @State(Scope.Thread) + public static class Initialized { + final java.util.ArrayList javaMutable = new java.util.ArrayList<>(); + scala.collection.immutable.List scalaPersistent = scala.collection.immutable.List$.MODULE$.empty(); + fj.data.List fjavaPersistent = fj.data.List.list(); + javaslang.collection.List slangPersistent = javaslang.collection.List.empty(); + + @Setup + public void initializeMutable(Base state) { + assertEquals(javaMutable.size(), 0); + Collections.addAll(javaMutable, state.ELEMENTS); + assertEquals(javaMutable.size(), state.CONTAINER_SIZE); + + assertEquals(fjavaPersistent.length(), 0); + assertEquals(scalaPersistent.size(), 0); + assertEquals(slangPersistent.size(), 0); + for (Integer element : state.ELEMENTS) { + fjavaPersistent = fjavaPersistent.cons(element); + scalaPersistent = scalaPersistent.$colon$colon(element); + slangPersistent = slangPersistent.prepend(element); + } + assertEquals(fjavaPersistent.length(), state.CONTAINER_SIZE); + assertEquals(scalaPersistent.size(), state.CONTAINER_SIZE); + assertEquals(slangPersistent.size(), state.CONTAINER_SIZE); + } + } + + @Benchmark + public Object java_mutable(Initialized state) { + return state.javaMutable.stream().collect(Collectors.groupingBy(Integer::bitCount)); + } + + @Benchmark + public Object scala_persistent(Initialized state) { + return state.scalaPersistent.groupBy(JFunction.func(Integer::bitCount)); + } + + @Benchmark + public Object fjava_persistent(Initialized state) { + return state.fjavaPersistent.groupBy(Integer::bitCount); + } + + @Benchmark + public Object slang_persistent(Initialized state) { + return state.slangPersistent.groupBy(Integer::bitCount); + } + } } \ No newline at end of file diff --git a/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/PriorityQueueBenchmark.java b/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/PriorityQueueBenchmark.java index e3ba4741f1..26b5086ceb 100644 --- a/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/PriorityQueueBenchmark.java +++ b/javaslang-benchmark/src/test/java/javaslang/benchmark/collection/PriorityQueueBenchmark.java @@ -1,29 +1,21 @@ package javaslang.benchmark.collection; import javaslang.Tuple2; -import javaslang.benchmark.JmhRunner; import javaslang.collection.Traversable; -import org.openjdk.jmh.annotations.Benchmark; -import org.openjdk.jmh.annotations.Level; -import org.openjdk.jmh.annotations.Param; -import org.openjdk.jmh.annotations.Scope; -import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.annotations.State; -import org.openjdk.jmh.annotations.TearDown; import scala.math.Ordering; import scala.math.Ordering$; -import scalaz.Heap; -import scalaz.Order; -import scalaz.Order$; +import scalaz.*; import java.util.Collections; -import java.util.Objects; -import java.util.Random; + +import static javaslang.benchmark.JmhRunner.*; public class PriorityQueueBenchmark { public static void main(String... args) { /* main is more reliable than a test */ - JmhRunner.run(PriorityQueueBenchmark.class); + run(PriorityQueueBenchmark.class); } @State(Scope.Benchmark) @@ -31,7 +23,7 @@ public static class Base { protected static final Ordering SCALA_ORDERING = Ordering$.MODULE$.comparatorToOrdering(Integer::compareTo); protected static final Order SCALAZ_ORDER = Order$.MODULE$.fromScalaOrdering(SCALA_ORDERING); - @Param({ "10", "100", "1000", "10000"}) + @Param({ "10", "100", "1000", "10000" }) public int CONTAINER_SIZE; public Integer[] ELEMENTS; @@ -39,19 +31,10 @@ public static class Base { @Setup public void setup() { - final Random random = new Random(0); - - ELEMENTS = new Integer[CONTAINER_SIZE]; - for (int i = 0; i < CONTAINER_SIZE; i++) { - final int value = random.nextInt(CONTAINER_SIZE) - (CONTAINER_SIZE / 2); - ELEMENTS[i] = value; - expectedAggregate ^= value; - } - } + ELEMENTS = getRandomValues(CONTAINER_SIZE, 0); - protected static void assertEquals(T a, T b) { - if (!Objects.equals(a, b)) { - throw new IllegalStateException(a + " != " + b); + for (int element : ELEMENTS) { + expectedAggregate ^= element; } } } From 0f1bf89ba9ec9b68e72a64446008b48e9508da33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pap=20L=C5=91rinc?= Date: Tue, 24 May 2016 02:24:38 +0300 Subject: [PATCH 2/2] Optimized `groupBy` operations --- .../javaslang/collection/Collections.java | 26 ++++++++++++++++--- .../java/javaslang/collection/Iterator.java | 12 +-------- .../main/java/javaslang/collection/List.java | 3 +-- .../main/java/javaslang/collection/Queue.java | 3 +-- .../java/javaslang/collection/Stream.java | 3 +-- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/javaslang/src/main/java/javaslang/collection/Collections.java b/javaslang/src/main/java/javaslang/collection/Collections.java index 267b39d53f..eaf477951b 100644 --- a/javaslang/src/main/java/javaslang/collection/Collections.java +++ b/javaslang/src/main/java/javaslang/collection/Collections.java @@ -7,7 +7,7 @@ import javaslang.control.Option; -import java.util.Objects; +import java.util.*; import java.util.function.*; /** @@ -37,14 +37,32 @@ static , T> C retainAll(C collection, Iterable> Map groupBy(Traversable collection, Function classifier, Function, R> mapper) { + Objects.requireNonNull(collection, "collection is null"); + Objects.requireNonNull(classifier, "classifier is null"); + Objects.requireNonNull(mapper, "mapper is null"); + + final java.util.Map> mutableResults = new java.util.HashMap<>(); + for (T value : collection) { + final C key = classifier.apply(value); + mutableResults.computeIfAbsent(key, k -> new ArrayList<>()).add(value); + } + + HashMap results = HashMap.empty(); + for (java.util.Map.Entry> entry : mutableResults.entrySet()) { + results = results.put(entry.getKey(), mapper.apply(entry.getValue())); + } + return results; + } + static Option indexOption(int index) { return Option.when(index >= 0, index); } // checks, if the *elements* of the given iterables are equal static boolean equals(Iterable iterable1, Iterable iterable2) { - java.util.Iterator iter1 = iterable1.iterator(); - java.util.Iterator iter2 = iterable2.iterator(); + final java.util.Iterator iter1 = iterable1.iterator(); + final java.util.Iterator iter2 = iterable2.iterator(); while (iter1.hasNext() && iter2.hasNext()) { if (!Objects.equals(iter1.next(), iter2.next())) { return false; @@ -101,7 +119,7 @@ static , T> C tabulate(int n, Function Map> groupBy(Function classifier) { - Objects.requireNonNull(classifier, "classifier is null"); - if (!hasNext()) { - return HashMap.empty(); - } else { - final Map> streams = foldLeft(HashMap.empty(), (map, entry) -> { - final C key = classifier.apply(entry); - final Stream values = map.get(key).map(entries -> entries.append(entry)).getOrElse(Stream.of(entry)); - return map.put(key, values); - }); - return streams.map((c, ts) -> Tuple.of(c, ts.iterator())); - } + return Collections.groupBy(this, classifier, Iterator::ofAll); } @Override diff --git a/javaslang/src/main/java/javaslang/collection/List.java b/javaslang/src/main/java/javaslang/collection/List.java index aa603402c3..ebaaee9ba2 100644 --- a/javaslang/src/main/java/javaslang/collection/List.java +++ b/javaslang/src/main/java/javaslang/collection/List.java @@ -669,8 +669,7 @@ default T get(int index) { @Override default Map> groupBy(Function classifier) { - Objects.requireNonNull(classifier, "classifier is null"); - return iterator().groupBy(classifier).map((c, it) -> Tuple.of(c, ofAll(it))); + return Collections.groupBy(this, classifier, List::ofAll); } @Override diff --git a/javaslang/src/main/java/javaslang/collection/Queue.java b/javaslang/src/main/java/javaslang/collection/Queue.java index 9972b8ef84..11c7d176a1 100644 --- a/javaslang/src/main/java/javaslang/collection/Queue.java +++ b/javaslang/src/main/java/javaslang/collection/Queue.java @@ -626,8 +626,7 @@ public T get(int index) { @Override public Map> groupBy(Function classifier) { - Objects.requireNonNull(classifier, "classifier is null"); - return iterator().groupBy(classifier).map((c, it) -> Tuple.of(c, ofAll(it))); + return Collections.groupBy(this, classifier, Queue::ofAll); } @Override diff --git a/javaslang/src/main/java/javaslang/collection/Stream.java b/javaslang/src/main/java/javaslang/collection/Stream.java index 801ad5c225..5bb3c72e78 100644 --- a/javaslang/src/main/java/javaslang/collection/Stream.java +++ b/javaslang/src/main/java/javaslang/collection/Stream.java @@ -862,8 +862,7 @@ default T get(int index) { @Override default Map> groupBy(Function classifier) { - Objects.requireNonNull(classifier, "classifier is null"); - return iterator().groupBy(classifier).map((c, it) -> Tuple.of(c, Stream.ofAll(it))); + return Collections.groupBy(this, classifier, Stream::ofAll); } @Override