From c9dcbdbe97094532ec9cf5a1494363ed36785d0c Mon Sep 17 00:00:00 2001 From: Andreas Huber Date: Sat, 7 Nov 2020 08:36:07 +0100 Subject: [PATCH] performance improvements the heap refill code was recursively implemented with two methods. I merged both methods. replace recursion in heap refill method with iterative approach use array for list of LongQueue This way there is no precondition when accessing the elements --- build.gradle | 8 +- .../collections/BenchmarkMultiwayMerge.java | 85 +++++++++++++++++++ .../collections/MultiwayLongMerger.java | 76 +++++++++-------- 3 files changed, 132 insertions(+), 37 deletions(-) create mode 100644 primitiveCollections/src/jmh/java/org/lucares/collections/BenchmarkMultiwayMerge.java diff --git a/build.gradle b/build.gradle index 86f30f9..5f5dee6 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { jcenter() } dependencies { - // run with gradle --no-daemon clean jmh + // run with Java 11 and ./gradlew --no-daemon clean jmh classpath "me.champeau.gradle:jmh-gradle-plugin:0.5.2" } } @@ -16,9 +16,13 @@ plugins { // usage: gradle dependencyUpdates -Drevision=release id "com.github.ben-manes.versions" version "0.34.0" } + apply plugin: 'java' apply plugin: 'eclipse' +// java compatibility version +sourceCompatibility = 11 + /* * The shared configuration for all sub-projects: */ @@ -78,7 +82,7 @@ subprojects { jmh { //jvmArgsAppend = "" resultFormat = "JSON" - //include = ['.*Union.*'] // include pattern (regular expression) for benchmarks to be executed + include = ['.*BenchmarkMultiwayMerge.*'] // include pattern (regular expression) for benchmarks to be executed //exclude = ['some regular expression'] // exclude pattern (regular expression) for benchmarks to be executed } } diff --git a/primitiveCollections/src/jmh/java/org/lucares/collections/BenchmarkMultiwayMerge.java b/primitiveCollections/src/jmh/java/org/lucares/collections/BenchmarkMultiwayMerge.java new file mode 100644 index 0000000..214458e --- /dev/null +++ b/primitiveCollections/src/jmh/java/org/lucares/collections/BenchmarkMultiwayMerge.java @@ -0,0 +1,85 @@ +package org.lucares.collections; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.LongStream; + +import org.lucares.collections.MultiwayLongMerger.LongQueue; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +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 org.openjdk.jmh.annotations.Warmup; + +@State(Scope.Benchmark) +@BenchmarkMode(Mode.Throughput) +@Warmup(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Fork(3) +public class BenchmarkMultiwayMerge { + + @Param({ "10000"/*, "20000" */}) + private int values; + + @Param({/*"3","5",*/"10"}) + private int numLists; + + + private List longSorted = null; + + @Setup + public void setup() throws Exception { + longSorted = new ArrayList<>(); + for(int i = 0; i < numLists; i++) { + LongList list = LongList.of(); + LongStream.range(0, values).forEachOrdered(list::add); + list.sort(); + longSorted.add(list); + } + } + + @TearDown + public void tearDown() { + longSorted = null; + } + + @Benchmark + public void testUnionSortedLists_MultiwayMerge() throws Exception { + + LongList.union(longSorted); + } + + //@Benchmark + public void testUnionSortedLists_TwowayMergeImplementation() throws Exception { + + twowayMerge(longSorted); + } + + private void twowayMerge(List longLists) { + LongList result = longLists.get(0); + for(int i =1; i < longLists.size(); i++) { + result = LongList.union(result, longLists.get(i)); + } + } + + public static void main(String[] args) throws Exception { + System.out.println("\n\n----------------\nstart"); + // -XX:+PrintCompilation + for (int i = 0; i < 200; i++) { + BenchmarkMultiwayMerge benchmark = new BenchmarkMultiwayMerge(); + benchmark.setup(); + //System.out.println("\n\n----------------\n"+i); + for (int j = 0; j < 100000; j++) { + benchmark.testUnionSortedLists_MultiwayMerge(); + } + } + System.out.println("done"); + } +} diff --git a/primitiveCollections/src/main/java/org/lucares/collections/MultiwayLongMerger.java b/primitiveCollections/src/main/java/org/lucares/collections/MultiwayLongMerger.java index 92dc945..58df30f 100644 --- a/primitiveCollections/src/main/java/org/lucares/collections/MultiwayLongMerger.java +++ b/primitiveCollections/src/main/java/org/lucares/collections/MultiwayLongMerger.java @@ -8,9 +8,9 @@ import java.util.List; class MultiwayLongMerger { private static final long UNSET = Long.MAX_VALUE; - private static class LongQueue { + static class LongQueue { private static final LongQueue EMPTY = new LongQueue(LongList.of()); - + final LongList wrapped; int offset = 0; @@ -109,7 +109,9 @@ class MultiwayLongMerger { private static class MinValuePriorityQueue { - private List longQueues; + private static final LongQueue[] EMPTY_QUEUE = new LongQueue[0]; + + private LongQueue[] longQueues; /* * a classic heap where the nodes are layed out in breath first order. First the @@ -122,7 +124,7 @@ class MultiwayLongMerger { private final int firstLeafIndex; public MinValuePriorityQueue(final Collection longQueues) { - this.longQueues = new ArrayList<>(longQueues); + final List tmpQueues = new ArrayList<>(longQueues); size = longQueues.size(); heap = new long[2 * nextPowOfTwo(size) - 1]; @@ -132,11 +134,12 @@ class MultiwayLongMerger { // fill the longQueues list with empty queues, so that we // have a queue for every leaf in the heap. This makes fillWithMinOfChildren() - // easier, because it removes special cases. - for (int i = size ; i < nextPowOfTwo(size); i++) { - this.longQueues.add(LongQueue.EMPTY); + // easier, because it removes special cases. + for (int i = size; i < nextPowOfTwo(size); i++) { + tmpQueues.add(LongQueue.EMPTY); } - + this.longQueues = tmpQueues.toArray(EMPTY_QUEUE); + init(); } @@ -180,7 +183,7 @@ class MultiwayLongMerger { // fill leaf nodes int offset = firstLeafIndex; for (int j = 0; j < size; j++) { - final LongQueue q = longQueues.get(j); + final LongQueue q = longQueues[j]; heap[offset + j] = q.isEmpty() ? UNSET : q.pop(); } @@ -211,37 +214,40 @@ class MultiwayLongMerger { } private void fillWithMinOfChildren(int index) { - final int leftChildIndex = index * 2 + 1; // leftChildIndex(index); - final int rightChildIndex = leftChildIndex + 1;// rightChildIndex(index); - - final long valueOfLeftChild = heap[leftChildIndex]; - final long valueOfRightChild = heap[rightChildIndex]; - - final int chosenValue; + int firstLeafOffset = firstLeafIndex; - if (valueOfLeftChild < valueOfRightChild) { - // left < right - heap[index] = valueOfLeftChild; - chosenValue = leftChildIndex; - } else { - // left >= right - heap[index] = valueOfRightChild; - chosenValue = rightChildIndex; + int currentIndex = index; + while (true) { + final int leftChildIndex = currentIndex * 2 + 1; // leftChildIndex(index); + final int rightChildIndex = leftChildIndex + 1;// rightChildIndex(index); + + final long valueOfLeftChild = heap[leftChildIndex]; + final long valueOfRightChild = heap[rightChildIndex]; + +// final boolean chooseLeft = valueOfLeftChild < valueOfRightChild; +// heap[currentIndex] = chooseLeft ? valueOfLeftChild : valueOfRightChild; +// currentIndex = chooseLeft ? leftChildIndex : rightChildIndex; + + if (valueOfLeftChild < valueOfRightChild) { + // left < right + heap[currentIndex] = valueOfLeftChild; + currentIndex = leftChildIndex; + } else { + // left >= right + heap[currentIndex] = valueOfRightChild; + currentIndex = rightChildIndex; + } + + if (currentIndex >= firstLeafOffset) { + final int listIndex = currentIndex - firstLeafIndex; // leafIndexToListIndex(index); + final LongQueue queue = longQueues[listIndex]; + heap[currentIndex] = queue.isEmpty() ? UNSET : queue.pop(); + return; + } } - - refillValue(chosenValue); } - private void refillValue(int index) { - if (isLeaf(index)) { - final int listIndex = index - firstLeafIndex; // leafIndexToListIndex(index); - final LongQueue queue = longQueues.get(listIndex); - heap[index] = queue.isEmpty() ? UNSET : queue.pop(); - return; - } - fillWithMinOfChildren(index); - } } public static void main(String[] args) {