diff --git a/java/src/main/java/org/softwareheritage/graph/utils/ForkJoinQuickSort3.java b/java/src/main/java/org/softwareheritage/graph/utils/ForkJoinQuickSort3.java new file mode 100644 --- /dev/null +++ b/java/src/main/java/org/softwareheritage/graph/utils/ForkJoinQuickSort3.java @@ -0,0 +1,217 @@ +package org.softwareheritage.graph.utils; + +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.concurrent.RecursiveAction; + +import static it.unimi.dsi.fastutil.longs.LongArrays.ensureSameLength; + +public class ForkJoinQuickSort3 extends RecursiveAction { + private static final long serialVersionUID = 1L; + private final int from; + private final int to; + private final long[] x, y, z; + + private static final int QUICKSORT_NO_REC = 16; + private static final int PARALLEL_QUICKSORT_NO_FORK = 8192; + private static final int QUICKSORT_MEDIAN_OF_9 = 128; + + public ForkJoinQuickSort3(final long[] x, final long[] y, final long z[], final int from, final int to) { + this.from = from; + this.to = to; + this.x = x; + this.y = y; + this.z = z; + } + + @Override + protected void compute() { + final long[] x = this.x; + final long[] y = this.y; + final long[] z = this.z; + final int len = to - from; + if (len < PARALLEL_QUICKSORT_NO_FORK) { + quickSort(x, y, z, from, to); + return; + } + // Choose a partition element, v + int m = from + len / 2; + int l = from; + int n = to - 1; + int s = len / 8; + l = med3(x, y, z, l, l + s, l + 2 * s); + m = med3(x, y, z, m - s, m, m + s); + n = med3(x, y, z, n - 2 * s, n - s, n); + m = med3(x, y, z, l, m, n); + final long xm = x[m], ym = y[m], zm = z[m]; + // Establish Invariant: v* (v)* v* + int a = from, b = a, c = to - 1, d = c; + while (true) { + int comparison, t; + while (b <= c && (comparison = compare(x, y, z, b, xm, ym, zm)) <= 0) { + if (comparison == 0) + swap(x, y, z, a++, b); + b++; + } + while (c >= b && (comparison = compare(x, y, z, c, xm, ym, zm)) >= 0) { + if (comparison == 0) + swap(x, y, z, c, d--); + c--; + } + if (b > c) + break; + swap(x, y, z, b++, c--); + } + // Swap partition elements back to middle + int t; + s = Math.min(a - from, b - a); + swap(x, y, z, from, b - s, s); + s = Math.min(d - c, to - d - 1); + swap(x, y, z, b, to - s, s); + s = b - a; + t = d - c; + // Recursively sort non-partition-elements + if (s > 1 && t > 1) + invokeAll(new ForkJoinQuickSort3(x, y, z, from, from + s), new ForkJoinQuickSort3(x, y, z, to - t, to)); + else if (s > 1) + invokeAll(new ForkJoinQuickSort3(x, y, z, from, from + s)); + else + invokeAll(new ForkJoinQuickSort3(x, y, z, to - t, to)); + } + + public static void quickSort(final long[] x, final long[] y, final long[] z, final int from, final int to) { + final int len = to - from; + if (len < QUICKSORT_NO_REC) { + selectionSort(x, y, z, from, to); + return; + } + // Choose a partition element, v + int m = from + len / 2; + int l = from; + int n = to - 1; + if (len > QUICKSORT_MEDIAN_OF_9) { // Big arrays, pseudomedian of 9 + int s = len / 8; + l = med3(x, y, z, l, l + s, l + 2 * s); + m = med3(x, y, z, m - s, m, m + s); + n = med3(x, y, z, n - 2 * s, n - s, n); + } + m = med3(x, y, z, l, m, n); // Mid-size, med of 3 + // Establish Invariant: v* (v)* v* + int a = from, b = a, c = to - 1, d = c; + final long xm = x[m], ym = y[m], zm = z[m]; + while (true) { + int comparison; + while (b <= c && (comparison = compare(x, y, z, b, xm, ym, zm)) <= 0) { + if (comparison == 0) + swap(x, y, z, a++, b); + b++; + } + while (c >= b && (comparison = compare(x, y, z, c, xm, ym, zm)) >= 0) { + if (comparison == 0) + swap(x, y, z, c, d--); + c--; + } + if (b > c) + break; + swap(x, y, z, b++, c--); + } + // Swap partition elements back to middle + int s; + s = Math.min(a - from, b - a); + swap(x, y, z, from, b - s, s); + s = Math.min(d - c, to - d - 1); + swap(x, y, z, b, to - s, s); + // Recursively sort non-partition-elements + if ((s = b - a) > 1) + quickSort(x, y, z, from, from + s); + if ((s = d - c) > 1) + quickSort(x, y, z, to - s, to); + } + + public static void quickSort(final long[] x, final long[] y, final long[] z) { + quickSort(x, y, z, 0, x.length); + } + + private static int compare(final long[] x, final long[] y, final long[] z, final int u, final int v) { + int tx, ty; + return (tx = Long.compare(x[u], x[v])) != 0 + ? tx + : ((ty = Long.compare(y[u], y[v])) != 0 ? ty : Long.compare(z[u], z[v])); + } + + private static int compare(final long[] x, final long[] y, final long[] z, final int i, final long xm, + final long ym, final long zm) { + int tx, ty; + return (tx = Long.compare(x[i], xm)) != 0 + ? tx + : ((ty = Long.compare(y[i], ym)) != 0 ? ty : Long.compare(z[i], zm)); + } + + private static void swap(final long[] x, final long[] y, final long[] z, final int a, final int b) { + final long t = x[a]; + final long u = y[a]; + final long v = z[a]; + x[a] = x[b]; + y[a] = y[b]; + z[a] = z[b]; + x[b] = t; + y[b] = u; + z[b] = v; + } + + private static void swap(final long[] x, final long[] y, final long[] z, int a, int b, final int n) { + for (int i = 0; i < n; i++, a++, b++) + swap(x, y, z, a, b); + } + + private static int med3(final long[] x, final long[] y, final long[] z, final int a, final int b, final int c) { + final int ab = compare(x, y, z, a, b); + final int ac = compare(x, y, z, a, c); + final int bc = compare(x, y, z, b, c); + return (ab < 0 ? (bc < 0 ? b : ac < 0 ? c : a) : (bc > 0 ? b : ac > 0 ? c : a)); + } + + public static void selectionSort(final long[] a, final long[] b, long[] c, final int from, final int to) { + for (int i = from; i < to - 1; i++) { + int m = i; + for (int j = i + 1; j < to; j++) + if (compare(a, b, c, j, m) < 0) + m = j; + if (m != i) { + long t = a[i]; + a[i] = a[m]; + a[m] = t; + t = b[i]; + b[i] = b[m]; + b[m] = t; + t = c[i]; + c[i] = c[m]; + c[m] = t; + } + } + } + + public static void selectionSort(final long[] x, final long[] y, final long[] z) { + selectionSort(x, y, z, 0, x.length); + } + + public static ForkJoinPool getPool() { + ForkJoinPool current = ForkJoinTask.getPool(); + return current == null ? ForkJoinPool.commonPool() : current; + } + + public static void parallelQuickSort(final long[] x, final long[] y, final long[] z) { + ensureSameLength(x, y); + ensureSameLength(x, z); + parallelQuickSort(x, y, z, 0, x.length); + } + + public static void parallelQuickSort(final long[] x, final long[] y, final long[] z, final int from, final int to) { + ForkJoinPool pool = getPool(); + if (to - from < PARALLEL_QUICKSORT_NO_FORK || pool.getParallelism() == 1) + quickSort(x, y, z, from, to); + else { + pool.invoke(new ForkJoinQuickSort3(x, y, z, from, to)); + } + } +} diff --git a/java/src/test/java/org/softwareheritage/graph/utils/ForkJoinQuickSort3Test.java b/java/src/test/java/org/softwareheritage/graph/utils/ForkJoinQuickSort3Test.java new file mode 100644 --- /dev/null +++ b/java/src/test/java/org/softwareheritage/graph/utils/ForkJoinQuickSort3Test.java @@ -0,0 +1,103 @@ +package org.softwareheritage.graph.utils; + +import it.unimi.dsi.fastutil.longs.LongArrays; +import org.junit.jupiter.api.Test; + +import java.util.Random; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ForkJoinQuickSort3Test { + private static long[] identity(final int n) { + final long[] perm = new long[n]; + for (int i = perm.length; i-- != 0;) + perm[i] = i; + return perm; + } + + private static void checkArraySorted(long[] x, long[] y, long[] z) { + checkArraySorted(x, y, z, 0, x.length); + } + + private static void checkArraySorted(long[] x, long[] y, long[] z, int from, int to) { + for (int i = to - 1; i-- != from;) + assertTrue(x[i] < x[i + 1] || x[i] == x[i + 1] && (y[i] < y[i + 1] || y[i] == y[i + 1] && z[i] <= z[i + 1]), + String.format("%d: <%d, %d, %d>, <%d, %d, %d>", i, x[i], y[i], z[i], x[i + 1], y[i + 1], z[i + 1])); + } + + @Test + public void testParallelQuickSort3() { + final long[][] d = new long[3][]; + + d[0] = new long[10]; + for (int i = d[0].length; i-- != 0;) + d[0][i] = 3 - i % 3; + d[1] = LongArrays.shuffle(identity(10), new Random(0)); + d[2] = LongArrays.shuffle(identity(10), new Random(1)); + ForkJoinQuickSort3.parallelQuickSort(d[0], d[1], d[2]); + checkArraySorted(d[0], d[1], d[2]); + + d[0] = new long[100000]; + for (int i = d[0].length; i-- != 0;) + d[0][i] = 100 - i % 100; + d[1] = LongArrays.shuffle(identity(100000), new Random(6)); + d[2] = LongArrays.shuffle(identity(100000), new Random(7)); + ForkJoinQuickSort3.parallelQuickSort(d[0], d[1], d[2]); + checkArraySorted(d[0], d[1], d[2]); + + d[0] = new long[10]; + for (int i = d[0].length; i-- != 0;) + d[0][i] = i % 3 - 2; + Random random = new Random(0); + d[1] = new long[d[0].length]; + for (int i = d[1].length; i-- != 0;) + d[1][i] = random.nextInt(); + d[2] = new long[d[0].length]; + for (int i = d[2].length; i-- != 0;) + d[2][i] = random.nextInt(); + ForkJoinQuickSort3.parallelQuickSort(d[0], d[1], d[2]); + checkArraySorted(d[0], d[1], d[2]); + + d[0] = new long[100000]; + d[1] = new long[100000]; + d[2] = new long[100000]; + for (int i = d[0].length; i-- != 0;) + d[2][i] = random.nextInt(); + ForkJoinQuickSort3.parallelQuickSort(d[0], d[1], d[2]); + checkArraySorted(d[0], d[1], d[2]); + + d[0] = new long[100000]; + random = new Random(0); + for (int i = d[0].length; i-- != 0;) + d[0][i] = random.nextInt(); + d[1] = new long[d[0].length]; + for (int i = d[1].length; i-- != 0;) + d[1][i] = random.nextInt(); + d[2] = new long[d[0].length]; + for (int i = d[2].length; i-- != 0;) + d[2][i] = random.nextInt(); + ForkJoinQuickSort3.parallelQuickSort(d[0], d[1], d[2]); + checkArraySorted(d[0], d[1], d[2]); + for (int i = 100; i-- != 10;) + d[0][i] = random.nextInt(); + for (int i = 100; i-- != 10;) + d[1][i] = random.nextInt(); + for (int i = 100; i-- != 10;) + d[2][i] = random.nextInt(); + ForkJoinQuickSort3.parallelQuickSort(d[0], d[1], d[2], 10, 100); + checkArraySorted(d[0], d[1], d[2], 10, 100); + + d[0] = new long[10000000]; + random = new Random(0); + for (int i = d[0].length; i-- != 0;) + d[0][i] = random.nextInt(); + d[1] = new long[d[0].length]; + for (int i = d[1].length; i-- != 0;) + d[1][i] = random.nextInt(); + d[2] = new long[d[0].length]; + for (int i = d[2].length; i-- != 0;) + d[2][i] = random.nextInt(); + ForkJoinQuickSort3.parallelQuickSort(d[0], d[1], d[2]); + checkArraySorted(d[0], d[1], d[2]); + } +}