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
This commit is contained in:
2020-11-07 08:36:07 +01:00
parent a427df09aa
commit c9dcbdbe97
3 changed files with 132 additions and 37 deletions

View File

@@ -7,7 +7,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { 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" classpath "me.champeau.gradle:jmh-gradle-plugin:0.5.2"
} }
} }
@@ -16,9 +16,13 @@ plugins {
// usage: gradle dependencyUpdates -Drevision=release // usage: gradle dependencyUpdates -Drevision=release
id "com.github.ben-manes.versions" version "0.34.0" id "com.github.ben-manes.versions" version "0.34.0"
} }
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'eclipse' apply plugin: 'eclipse'
// java compatibility version
sourceCompatibility = 11
/* /*
* The shared configuration for all sub-projects: * The shared configuration for all sub-projects:
*/ */
@@ -78,7 +82,7 @@ subprojects {
jmh { jmh {
//jvmArgsAppend = "" //jvmArgsAppend = ""
resultFormat = "JSON" 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 //exclude = ['some regular expression'] // exclude pattern (regular expression) for benchmarks to be executed
} }
} }

View File

@@ -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<LongList> 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<LongList> 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");
}
}

View File

@@ -8,7 +8,7 @@ import java.util.List;
class MultiwayLongMerger { class MultiwayLongMerger {
private static final long UNSET = Long.MAX_VALUE; private static final long UNSET = Long.MAX_VALUE;
private static class LongQueue { static class LongQueue {
private static final LongQueue EMPTY = new LongQueue(LongList.of()); private static final LongQueue EMPTY = new LongQueue(LongList.of());
final LongList wrapped; final LongList wrapped;
@@ -109,7 +109,9 @@ class MultiwayLongMerger {
private static class MinValuePriorityQueue { private static class MinValuePriorityQueue {
private List<LongQueue> 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 * 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; private final int firstLeafIndex;
public MinValuePriorityQueue(final Collection<LongQueue> longQueues) { public MinValuePriorityQueue(final Collection<LongQueue> longQueues) {
this.longQueues = new ArrayList<>(longQueues); final List<LongQueue> tmpQueues = new ArrayList<>(longQueues);
size = longQueues.size(); size = longQueues.size();
heap = new long[2 * nextPowOfTwo(size) - 1]; heap = new long[2 * nextPowOfTwo(size) - 1];
@@ -133,9 +135,10 @@ class MultiwayLongMerger {
// fill the longQueues list with empty queues, so that we // fill the longQueues list with empty queues, so that we
// have a queue for every leaf in the heap. This makes fillWithMinOfChildren() // have a queue for every leaf in the heap. This makes fillWithMinOfChildren()
// easier, because it removes special cases. // easier, because it removes special cases.
for (int i = size ; i < nextPowOfTwo(size); i++) { for (int i = size; i < nextPowOfTwo(size); i++) {
this.longQueues.add(LongQueue.EMPTY); tmpQueues.add(LongQueue.EMPTY);
} }
this.longQueues = tmpQueues.toArray(EMPTY_QUEUE);
init(); init();
} }
@@ -180,7 +183,7 @@ class MultiwayLongMerger {
// fill leaf nodes // fill leaf nodes
int offset = firstLeafIndex; int offset = firstLeafIndex;
for (int j = 0; j < size; j++) { 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(); heap[offset + j] = q.isEmpty() ? UNSET : q.pop();
} }
@@ -211,37 +214,40 @@ class MultiwayLongMerger {
} }
private void fillWithMinOfChildren(int index) { 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]; int firstLeafOffset = firstLeafIndex;
final long valueOfRightChild = heap[rightChildIndex];
final int chosenValue; 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];
if (valueOfLeftChild < valueOfRightChild) { // final boolean chooseLeft = valueOfLeftChild < valueOfRightChild;
// left < right // heap[currentIndex] = chooseLeft ? valueOfLeftChild : valueOfRightChild;
heap[index] = valueOfLeftChild; // currentIndex = chooseLeft ? leftChildIndex : rightChildIndex;
chosenValue = leftChildIndex;
} else { if (valueOfLeftChild < valueOfRightChild) {
// left >= right // left < right
heap[index] = valueOfRightChild; heap[currentIndex] = valueOfLeftChild;
chosenValue = rightChildIndex; 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) { public static void main(String[] args) {