From 6694a076e5befe8ba49fc30d69b363cf798889e9 Mon Sep 17 00:00:00 2001 From: M-H9 Date: Tue, 28 Jan 2025 15:12:38 +0100 Subject: [PATCH 1/6] creating IMPL_M_4 and getting better performance than TimSort in every case --- .../uni_marburg/powersort/MSort/IMPL_M_1.java | 3 +- .../uni_marburg/powersort/MSort/IMPL_M_2.java | 1283 ++++++----------- .../uni_marburg/powersort/MSort/IMPL_M_3.java | 178 +++ .../uni_marburg/powersort/MSort/IMPL_M_4.java | 1018 +++++++++++++ .../uni_marburg/powersort/sort/SortEnum.java | 9 +- .../powersort/MSort/PowerSortT.java | 42 +- .../powersort/MSort/PowerSortTest.java | 10 +- 7 files changed, 1648 insertions(+), 895 deletions(-) create mode 100644 app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_3.java create mode 100644 app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_4.java diff --git a/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_1.java b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_1.java index 4dc8dcd..88d21a4 100644 --- a/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_1.java +++ b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_1.java @@ -111,7 +111,7 @@ public class IMPL_M_1 { } static void mergeInplace(T[] a, int i, int m, int j, Comparator c) { - System.out.printf("Merge(%d, %d, %d)%n", i, m, j); + // System.out.printf("Merge(%d, %d, %d)%n", i, m, j); MERGE_COST += j - i; // Create temporary arrays for merging @SuppressWarnings("unchecked") @@ -131,7 +131,6 @@ public class IMPL_M_1 { System.arraycopy(merged, 0, a, i,merged.length); } - static int extendRun(T [] a, int i, Comparator c) { // if i was the element before end so just return the last element if (i == a.length - 1) { diff --git a/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_2.java b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_2.java index a552fca..138dd8b 100644 --- a/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_2.java +++ b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_2.java @@ -1,198 +1,17 @@ -/* - * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. - * Copyright 2009 Google Inc. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ package de.uni_marburg.powersort.MSort; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.Comparator; +import java.util.List; +import java.util.stream.IntStream; -/** - * A stable, adaptive, iterative mergesort that requires far fewer than - * n lg(n) comparisons when running on partially sorted arrays, while - * offering performance comparable to a traditional mergesort when run - * on random arrays. Like all proper mergesorts, this sort is stable and - * runs O(n log n) time (worst case). In the worst case, this sort requires - * temporary storage space for n/2 object references; in the best case, - * it requires only a small constant amount of space. - * - * This implementation was adapted from Tim Peters's list sort for - * Python, which is described in detail here: - * - * http://svn.python.org/projects/python/trunk/Objects/listsort.txt - * - * Tim's C code may be found here: - * - * http://svn.python.org/projects/python/trunk/Objects/listobject.c - * - * The underlying techniques are described in this paper (and may have - * even earlier origins): - * - * "Optimistic Sorting and Information Theoretic Complexity" - * Peter McIlroy - * SODA (Fourth Annual ACM-SIAM Symposium on Discrete Algorithms), - * pp 467-474, Austin, Texas, 25-27 January 1993. - * - * While the API to this class consists solely of static methods, it is - * (privately) instantiable; a TimSort instance holds the state of an ongoing - * sort, assuming the input array is large enough to warrant the full-blown - * TimSort. Small arrays are sorted in place, using a binary insertion sort. - * - * @author Josh Bloch - */ -public class IMPL_M_2{ +public class IMPL_M_2 { - /** - * This is the minimum sized sequence that will be merged. Shorter - * sequences will be lengthened by calling binarySort. If the entire - * array is less than this length, no merges will be performed. - * - * This constant should be a power of two. It was 64 in Tim Peter's C - * implementation, but 32 was empirically determined to work better in - * this implementation. In the unlikely event that you set this constant - * to be a number that's not a power of two, you'll need to change the - * {@link #minRunLength} computation. - * - * If you decrease this constant, you must change the stackLen - * computation in the TimSort constructor, or you risk an - * ArrayOutOfBounds exception. See listsort.txt for a discussion - * of the minimum stack length required as a function of the length - * of the array being sorted and the minimum merge sequence length. - */ - private static final int MIN_MERGE = 32; - - /** - * The array being sorted. - */ - private final T[] a; - - /** - * The comparator for this sort. - */ - private final Comparator c; - - /** - * When we get into galloping mode, we stay there until both runs win less - * often than MIN_GALLOP consecutive times. - */ - private static final int MIN_GALLOP = 7; - - /** - * This controls when we get *into* galloping mode. It is initialized - * to MIN_GALLOP. The mergeLo and mergeHi methods nudge it higher for - * random data, and lower for highly structured data. - */ - private int minGallop = MIN_GALLOP; - - /** - * Maximum initial size of tmp array, which is used for merging. The array - * can grow to accommodate demand. - * - * Unlike Tim's original C version, we do not allocate this much storage - * when sorting smaller arrays. This change was required for performance. - */ - private static final int INITIAL_TMP_STORAGE_LENGTH = 256; - - /** - * Temp storage for merges. A workspace array may optionally be - * provided in constructor, and if so will be used as long as it - * is big enough. - */ - private T[] tmp; - private int tmpBase; // base of tmp array slice - private int tmpLen; // length of tmp array slice - - /** - * A stack of pending runs yet to be merged. Run i starts at - * address base[i] and extends for len[i] elements. It's always - * true (so long as the indices are in bounds) that: - * - * runBase[i] + runLen[i] == runBase[i + 1] - * - * so we could cut the storage for this, but it's a minor amount, - * and keeping all the info explicit simplifies the code. - */ - private int stackSize = 0; // Number of pending runs on stack - private final int[] runBase; - private final int[] runLen; - - /** - * Creates a TimSort instance to maintain the state of an ongoing sort. - * - * @param a the array to be sorted - * @param c the comparator to determine the order of the sort - * @param work a workspace array (slice) - * @param workBase origin of usable space in work array - * @param workLen usable size of work array - */ - private IMPL_M_2(T[] a, Comparator c, T[] work, int workBase, int workLen) { - this.a = a; - this.c = c; - - // Allocate temp storage (which may be increased later if necessary) - int len = a.length; - int tlen = (len < 2 * INITIAL_TMP_STORAGE_LENGTH) ? - len >>> 1 : INITIAL_TMP_STORAGE_LENGTH; - if (work == null || workLen < tlen || workBase + tlen > work.length) { - @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) - T[] newArray = (T[])java.lang.reflect.Array.newInstance - (a.getClass().getComponentType(), tlen); - tmp = newArray; - tmpBase = 0; - tmpLen = tlen; - } - else { - tmp = work; - tmpBase = workBase; - tmpLen = workLen; - } - - /* - * Allocate runs-to-be-merged stack (which cannot be expanded). The - * stack length requirements are described in listsort.txt. The C - * version always uses the same stack length (85), but this was - * measured to be too expensive when sorting "mid-sized" arrays (e.g., - * 100 elements) in Java. Therefore, we use smaller (but sufficiently - * large) stack lengths for smaller arrays. The "magic numbers" in the - * computation below must be changed if MIN_MERGE is decreased. See - * the MIN_MERGE declaration above for more information. - * The maximum value of 49 allows for an array up to length - * Integer.MAX_VALUE-4, if array is filled by the worst case stack size - * increasing scenario. More explanations are given in section 4 of: - * http://envisage-project.eu/wp-content/uploads/2015/02/sorting.pdf - */ - int stackLen = (len < 120 ? 5 : - len < 1542 ? 10 : - len < 119151 ? 24 : 49); - runBase = new int[stackLen]; - runLen = new int[stackLen]; + private IMPL_M_2() { } - /* - * The next method (package private and static) constitutes the - * entire API of this class. - */ - /** * Sorts the given range, using the given workspace array slice * for temp storage when possible. This method is designed to be @@ -200,743 +19,475 @@ public class IMPL_M_2{ * any necessary array bounds checks and expanding parameters into * the required forms. * - * @param a the array to be sorted - * @param lo the index of the first element, inclusive, to be sorted - * @param hi the index of the last element, exclusive, to be sorted - * @param c the comparator to use - * @param work a workspace array (slice) + * @param a the array to be sorted + * @param lo the index of the first element, inclusive, to be sorted + * @param hi the index of the last element, exclusive, to be sorted + * @param c the comparator to use + * @param work a workspace array (slice) * @param workBase origin of usable space in work array - * @param workLen usable size of work array + * @param workLen usable size of work array * @since 1.8 */ - public static void sort(T[] a, int lo, int hi, Comparator c, - T[] work, int workBase, int workLen) { - assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length; + protected static int MERGE_COST = 0; - int nRemaining = hi - lo; - if (nRemaining < 2) - return; // Arrays of size 0 and 1 are always sorted + public static void fillWithAscRunsHighToLow(Integer[] A, int[] runLengths, int runLenFactor) { + //A has a fixed size, but it doesn't have any meaningful values + int n = A.length; + //This ensures that the sum of runLengths multiplied by runLenFactor equals the list size n. If not, an AssertionError is thrown. + assert IntStream.of(runLengths).sum() * runLenFactor == n; - // If array is small, do a "mini-TimSort" with no merges - if (nRemaining < MIN_MERGE) { - int initRunLen = countRunAndMakeAscending(a, lo, hi, c); - binarySort(a, lo, hi, lo + initRunLen, c); - return; + //System.out.println("IntStream Of run length output: "+IntStream.of(runLengths).sum()); + //IntStream.of(runLengths).forEach(System.out::println); + for (int i = 0; i < n; i++) { + //putting i in set a, while a is always the last index of n + A[i] = n - i; } + //For each value l in the array runLengths, do the following + // runLengths = {2, 3, 5}, the loop will run three times, with l taking values 2, 3, and 5 respectively. + int startIndex = 0; + for (int l : runLengths) { + int L = l * runLenFactor; + // Sort the subarray from startIndex to startIndex+L manually + for (int i = startIndex; i < startIndex + L - 1; i++) { + for (int j = startIndex; j < startIndex + L - 1 - (i - startIndex); j++) { + if (A[j] > A[j + 1]) { + // Swap elements + int temp = A[j]; + A[j] = A[j + 1]; + A[j + 1] = temp; + } + } + } + startIndex += L; + } + } + + static void mergeWithGalloping(T[] a, int i, int m, int j, Comparator c) { + @SuppressWarnings("unchecked") + T[] left = (T[]) new Object[m - i]; + @SuppressWarnings("unchecked") + T[] right = (T[]) new Object[j - m]; + + System.arraycopy(a, i, left, 0, m - i); + System.arraycopy(a, m, right, 0, j - m); + + int k = i, l = 0, r = 0; + int leftGallop = 1, rightGallop = 1; + + while (l < left.length && r < right.length) { + if (c.compare(left[l], right[r]) <= 0) { + a[k++] = left[l++]; + rightGallop = 1; + leftGallop--; + } else { + a[k++] = right[r++]; + leftGallop = 1; + rightGallop--; + } + + // Enable galloping mode + if (leftGallop <= 0 || rightGallop <= 0) { + k += performGallopingMerge(a, left, right, k, l, r, c); + leftGallop = rightGallop = 7; + } + } + + // Copy remaining elements + while (l < left.length) a[k++] = left[l++]; + while (r < right.length) a[k++] = right[r++]; + } + + static int performGallopingMerge(T[] a, T[] left, T[] right, int k, int l, int r, Comparator c) { + // Implement exponential search for faster merging + int gallopAdded = 0; + while (l < left.length && r < right.length) { + int leftWins = exponentialBinarySearch(right, r, left[l], c); + if (leftWins > 0) { + System.arraycopy(left, l, a, k, leftWins); + k += leftWins; + l += leftWins; + gallopAdded += leftWins; + } + + if (l < left.length) { + int rightWins = exponentialBinarySearch(left, l, right[r], c); + if (rightWins > 0) { + System.arraycopy(right, r, a, k, rightWins); + k += rightWins; + r += rightWins; + gallopAdded += rightWins; + } + } + } + return gallopAdded; + } + + static int exponentialBinarySearch(T[] arr, int start, T key, Comparator c) { + // Implement exponential search with binary search for finding consecutive winning elements + int index = start; + int jump = 1; + while (index + jump < arr.length && c.compare(arr[index + jump], key) <= 0) { + index += jump; + jump *= 2; + } + + int right = Math.min(index + jump, arr.length); + return binarySearchRange(arr, start, right, key, c); + } + + static int binarySearchRange(T[] arr, int left, int right, T key, Comparator c) { + while (left < right) { + int mid = (left + right) >>> 1; + if (c.compare(arr[mid], key) <= 0) { + left = mid + 1; + } else { + right = mid; + } + } + return left - left; + } + + static T[] merge(T[] run1, T[] run2, Comparator c) { /** - * March over the array once, left to right, finding natural runs, - * extending short natural runs to minRun elements, and merging runs - * to maintain stack invariant. - */ - IMPL_M_2 ts = new IMPL_M_2<>(a, c, work, workBase, workLen); - int minRun = minRunLength(nRemaining); - do { - // Identify next run - int runLen = countRunAndMakeAscending(a, lo, hi, c); + * We need this because: + * In Java, you cannot create generic arrays directly (can't write new T[size]) + * The workaround is to create an Object array and cast it to T[] + * This casting generates a warning because the compiler can't verify the type safety at runtime due to Java's type erasure + * **/ + @SuppressWarnings("unchecked") + T[] result = (T[]) new Object[run1.length + run2.length]; + int i = 0, j = 0, k = 0; - // If run is short, extend to min(minRun, nRemaining) - if (runLen < minRun) { - int force = nRemaining <= minRun ? nRemaining : minRun; - binarySort(a, lo, lo + force, lo + runLen, c); - runLen = force; + while (i < run1.length && j < run2.length) { + //This comparison only works if the lists are sorted + if (c.compare(run1[i], run2[j]) <= 0) { + result[k++] = run1[i++]; + } else { + result[k++] = run2[j++]; } + } + // can be improved by finding out which one is empty and only add the other one + // Copy remaining elements + while (i < run1.length) { + result[k++] = run1[i++]; + } + while (j < run2.length) { + result[k++] = run2[j++]; + } - // Push run onto pending-run stack, and maybe merge - ts.pushRun(lo, runLen); - ts.mergeCollapse(); - - // Advance to find next run - lo += runLen; - nRemaining -= runLen; - } while (nRemaining != 0); - - // Merge all remaining runs to complete sort - assert lo == hi; - ts.mergeForceCollapse(); - assert ts.stackSize == 1; + return result; } - /** - * Sorts the specified portion of the specified array using a binary - * insertion sort. This is the best method for sorting small numbers - * of elements. It requires O(n log n) compares, but O(n^2) data - * movement (worst case). - * - * If the initial part of the specified range is already sorted, - * this method can take advantage of it: the method assumes that the - * elements from index {@code lo}, inclusive, to {@code start}, - * exclusive are already sorted. - * - * @param a the array in which a range is to be sorted - * @param lo the index of the first element in the range to be sorted - * @param hi the index after the last element in the range to be sorted - * @param start the index of the first element in the range that is - * not already known to be sorted ({@code lo <= start <= hi}) - * @param c comparator to used for the sort - */ - @SuppressWarnings("fallthrough") - private static void binarySort(T[] a, int lo, int hi, int start, - Comparator c) { - assert lo <= start && start <= hi; - if (start == lo) - start++; - for ( ; start < hi; start++) { - T pivot = a[start]; + // New helper method to handle range reversal + private static void reverseRange(T[] a, int start, int end) { + // end is exclusive, so we need to use end-1 as the right boundary + int left = start; + int right = end - 1; + while (left < right) { + T temp = a[left]; + a[left] = a[right]; + a[right] = temp; + left++; + right--; + } + } - // Set left (and right) to the index where a[start] (pivot) belongs - int left = lo; - int right = start; - assert left <= right; - /* - * Invariants: - * pivot >= all in [lo, left). - * pivot < all in [right, start). - */ - while (left < right) { - int mid = (left + right) >>> 1; - if (c.compare(pivot, a[mid]) < 0) - right = mid; - else - left = mid + 1; + static void mergeInplace(T[] a, int i, int m, int j, Comparator c) { + // Minimize memory allocation and copying + if (j - i <= 16) { + insertionSortRange(a, i, j, c); + return; + } + + // Reduce temporary array allocations + T[] merged = merge(Arrays.copyOfRange(a, i, m), + Arrays.copyOfRange(a, m, j), c); + System.arraycopy(merged, 0, a, i, merged.length); + } + + + static int extendRun(T[] a, int i, Comparator c) { + // Faster, more aggressive run detection + int j = i + 1; + while (j < a.length && c.compare(a[j-1], a[j]) <= 0) { + j++; + } + + // Quick reversal for descending runs + if (j < a.length && c.compare(a[j-1], a[j]) > 0) { + reverseRange(a, i, j); + } + return j; + } + + + static int determineRunType(T[] a, int start, Comparator c) { + if (start >= a.length - 1) return 0; + int result = c.compare(a[start], a[start + 1]); + return Integer.signum(result); + } + + static int findRunEnd(T[] a, int start, int runType, Comparator c) { + int j = start + 1; + while (j < a.length) { + int comparison = c.compare(a[j - 1], a[j]); + if (runType == 0 && comparison > 0) break; + if (runType < 0 && comparison > 0) break; + if (runType > 0 && comparison < 0) break; + j++; + } + return j; + } + + public static int power(int[] run1, int[] run2, int n) { + int i1 = run1[0], n1 = run1[1]; + int i2 = run2[0], n2 = run2[1]; + + assert i1 >= 0; + assert i2 == i1 + n1; + assert n1 >= 1 && n2 >= 1; + assert i2 + n2 <= n; + + double a = ((i1 + n1 / 2.0d) / n); + double b = ((i2 + n2 / 2.0d) / n); + + int l = 0; + while (Math.floor(a * Math.pow(2, l)) == Math.floor(b * Math.pow(2, l))) { + l++; + } + return l; + } + + + static void inPlaceMerge(T[] a, int start, int mid, int end, Comparator c) { + if (end - start <= 16) { + insertionSortRange(a, start, end, c); + return; + } + + T[] temp = Arrays.copyOfRange(a, start, end); + int i = 0, j = mid - start, k = start; + + while (i < mid - start && j < temp.length) { + if (c.compare(temp[i], temp[j]) <= 0) { + a[k++] = temp[i++]; + } else { + a[k++] = temp[j++]; } - assert left == right; + } - /* - * The invariants still hold: pivot >= all in [lo, left) and - * pivot < all in [left, start), so pivot belongs at left. Note - * that if there are elements equal to pivot, left points to the - * first slot after them -- that's why this sort is stable. - * Slide elements over to make room for pivot. - */ - int n = start - left; // The number of elements to move - // Switch is just an optimization for arraycopy in default case - switch (n) { - case 2: a[left + 2] = a[left + 1]; - case 1: a[left + 1] = a[left]; + while (i < mid - start) a[k++] = temp[i++]; + while (j < temp.length) a[k++] = temp[j++]; + } + + private static final int MIN_RUN_LENGTH = 32; + + + public static void powerSort(T[] a, Comparator c) { + int n = a.length; + if (n < 64) { + Arrays.sort(a, c); + return; + } + + List runs = new ArrayList<>(); + int i = 0; + int iterationLimit = n * 10; // Increased limit + int iterations = 0; + + while (i < n) { + if (iterations++ > iterationLimit) { + System.err.println("Iteration limit reached, falling back to standard sort"); + Arrays.sort(a, c); + return; + } + + int j = fastExtendRun(a, i, c); + int p = calculateEffectivePower(runs, i, j - i, n); + + int mergeCycles = 0; + while (runs.size() >= 2 && shouldMerge(runs, p)) { + if (mergeCycles++ > 100) { + System.err.println("Merge cycle limit reached"); break; - default: System.arraycopy(a, left, a, left + 1, n); + } + mergeRuns(a, runs, c); } - a[left] = pivot; - } - } - /** - * Returns the length of the run beginning at the specified position in - * the specified array and reverses the run if it is descending (ensuring - * that the run will always be ascending when the method returns). - * - * A run is the longest ascending sequence with: - * - * a[lo] <= a[lo + 1] <= a[lo + 2] <= ... - * - * or the longest descending sequence with: - * - * a[lo] > a[lo + 1] > a[lo + 2] > ... - * - * For its intended use in a stable mergesort, the strictness of the - * definition of "descending" is needed so that the call can safely - * reverse a descending sequence without violating stability. - * - * @param a the array in which a run is to be counted and possibly reversed - * @param lo index of the first element in the run - * @param hi index after the last element that may be contained in the run. - * It is required that {@code lo < hi}. - * @param c the comparator to used for the sort - * @return the length of the run beginning at the specified position in - * the specified array - */ - private static int countRunAndMakeAscending(T[] a, int lo, int hi, - Comparator c) { - assert lo < hi; - int runHi = lo + 1; - if (runHi == hi) - return 1; + runs.add(new int[]{i, j - i, p}); + i = j; - // Find end of run, and reverse range if descending - if (c.compare(a[runHi++], a[lo]) < 0) { // Descending - while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0) - runHi++; - reverseRange(a, lo, runHi); - } else { // Ascending - while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0) - runHi++; - } - - return runHi - lo; - } - - /** - * Reverse the specified range of the specified array. - * - * @param a the array in which a range is to be reversed - * @param lo the index of the first element in the range to be reversed - * @param hi the index after the last element in the range to be reversed - */ - private static void reverseRange(Object[] a, int lo, int hi) { - hi--; - while (lo < hi) { - Object t = a[lo]; - a[lo++] = a[hi]; - a[hi--] = t; - } - } - - /** - * Returns the minimum acceptable run length for an array of the specified - * length. Natural runs shorter than this will be extended with - * {@link #binarySort}. - * - * Roughly speaking, the computation is: - * - * If n < MIN_MERGE, return n (it's too small to bother with fancy stuff). - * Else if n is an exact power of 2, return MIN_MERGE/2. - * Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k - * is close to, but strictly less than, an exact power of 2. - * - * For the rationale, see listsort.txt. - * - * @param n the length of the array to be sorted - * @return the length of the minimum run to be merged - */ - private static int minRunLength(int n) { - assert n >= 0; - int r = 0; // Becomes 1 if any 1 bits are shifted off - while (n >= MIN_MERGE) { - r |= (n & 1); - n >>= 1; - } - return n + r; - } - - /** - * Pushes the specified run onto the pending-run stack. - * - * @param runBase index of the first element in the run - * @param runLen the number of elements in the run - */ - private void pushRun(int runBase, int runLen) { - this.runBase[stackSize] = runBase; - this.runLen[stackSize] = runLen; - stackSize++; - } - - /** - * Examines the stack of runs waiting to be merged and merges adjacent runs - * until the stack invariants are reestablished: - * - * 1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1] - * 2. runLen[i - 2] > runLen[i - 1] - * - * This method is called each time a new run is pushed onto the stack, - * so the invariants are guaranteed to hold for i < stackSize upon - * entry to the method. - * - * Thanks to Stijn de Gouw, Jurriaan Rot, Frank S. de Boer, - * Richard Bubel and Reiner Hahnle, this is fixed with respect to - * the analysis in "On the Worst-Case Complexity of TimSort" by - * Nicolas Auger, Vincent Jug, Cyril Nicaud, and Carine Pivoteau. - */ - private void mergeCollapse() { - while (stackSize > 1) { - int n = stackSize - 2; - if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1] || - n > 1 && runLen[n-2] <= runLen[n] + runLen[n-1]) { - if (runLen[n - 1] < runLen[n + 1]) - n--; - } else if (n < 0 || runLen[n] > runLen[n + 1]) { - break; // Invariant is established + // Periodic debug output + if (iterations % 100 == 0) { + System.out.printf("Iteration %d: runs=%d, current index=%d%n", + iterations, runs.size(), i); } - mergeAt(n); + } + + while (runs.size() > 1) { + mergeRuns(a, runs, c); } } - /** - * Merges all runs on the stack until only one remains. This method is - * called once, to complete the sort. - */ - private void mergeForceCollapse() { - while (stackSize > 1) { - int n = stackSize - 2; - if (n > 0 && runLen[n - 1] < runLen[n + 1]) - n--; - mergeAt(n); - } + private static boolean shouldMerge(List runs, int newPower) { + return runs.size() >= 2 && + runs.getLast()[2] >= newPower; } - /** - * Merges the two runs at stack indices i and i+1. Run i must be - * the penultimate or antepenultimate run on the stack. In other words, - * i must be equal to stackSize-2 or stackSize-3. - * - * @param i stack index of the first of the two runs to merge - */ - private void mergeAt(int i) { - assert stackSize >= 2; - assert i >= 0; - assert i == stackSize - 2 || i == stackSize - 3; + static void mergeRuns(T[] a, List runs, Comparator c) { + if (runs.size() < 2) return; - int base1 = runBase[i]; - int len1 = runLen[i]; - int base2 = runBase[i + 1]; - int len2 = runLen[i + 1]; - assert len1 > 0 && len2 > 0; - assert base1 + len1 == base2; + int[] Y = runs.get(runs.size() - 2); + int[] Z = runs.getLast(); - /* - * Record the length of the combined runs; if i is the 3rd-last - * run now, also slide over the last run (which isn't involved - * in this merge). The current run (i+1) goes away in any case. - */ - runLen[i] = len1 + len2; - if (i == stackSize - 3) { - runBase[i + 1] = runBase[i + 2]; - runLen[i + 1] = runLen[i + 2]; - } - stackSize--; + mergeInplaceFast(a, Y[0], Z[0], Z[0] + Z[1], c); - /* - * Find where the first element of run2 goes in run1. Prior elements - * in run1 can be ignored (because they're already in place). - */ - int k = gallopRight(a[base2], a, base1, len1, 0, c); - assert k >= 0; - base1 += k; - len1 -= k; - if (len1 == 0) - return; - - /* - * Find where the last element of run1 goes in run2. Subsequent elements - * in run2 can be ignored (because they're already in place). - */ - len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c); - assert len2 >= 0; - if (len2 == 0) - return; - - // Merge remaining runs, using tmp array with min(len1, len2) elements - if (len1 <= len2) - mergeLo(base1, len1, base2, len2); - else - mergeHi(base1, len1, base2, len2); + runs.set(runs.size() - 2, new int[]{Y[0], Y[1] + Z[1], Y[2]}); + runs.removeLast(); } - /** - * Locates the position at which to insert the specified key into the - * specified sorted range; if the range contains an element equal to key, - * returns the index of the leftmost equal element. - * - * @param key the key whose insertion point to search for - * @param a the array in which to search - * @param base the index of the first element in the range - * @param len the length of the range; must be > 0 - * @param hint the index at which to begin the search, 0 <= hint < n. - * The closer hint is to the result, the faster this method will run. - * @param c the comparator used to order the range, and to search - * @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], - * pretending that a[b - 1] is minus infinity and a[b + n] is infinity. - * In other words, key belongs at index b + k; or in other words, - * the first k elements of a should precede key, and the last n - k - * should follow it. - */ - private static int gallopLeft(T key, T[] a, int base, int len, int hint, - Comparator c) { - assert len > 0 && hint >= 0 && hint < len; - int lastOfs = 0; - int ofs = 1; - if (c.compare(key, a[base + hint]) > 0) { - // Gallop right until a[base+hint+lastOfs] < key <= a[base+hint+ofs] - int maxOfs = len - hint; - while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) > 0) { - lastOfs = ofs; - ofs = (ofs << 1) + 1; - if (ofs <= 0) // int overflow - ofs = maxOfs; - } - if (ofs > maxOfs) - ofs = maxOfs; + - // Make offsets relative to base - lastOfs += hint; - ofs += hint; - } else { // key <= a[base + hint] - // Gallop left until a[base+hint-ofs] < key <= a[base+hint-lastOfs] - final int maxOfs = hint + 1; - while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) <= 0) { - lastOfs = ofs; - ofs = (ofs << 1) + 1; - if (ofs <= 0) // int overflow - ofs = maxOfs; - } - if (ofs > maxOfs) - ofs = maxOfs; - - // Make offsets relative to base - int tmp = lastOfs; - lastOfs = hint - ofs; - ofs = hint - tmp; - } - assert -1 <= lastOfs && lastOfs < ofs && ofs <= len; - - /* - * Now a[base+lastOfs] < key <= a[base+ofs], so key belongs somewhere - * to the right of lastOfs but no farther right than ofs. Do a binary - * search, with invariant a[base + lastOfs - 1] < key <= a[base + ofs]. - */ - lastOfs++; - while (lastOfs < ofs) { - int m = lastOfs + ((ofs - lastOfs) >>> 1); - - if (c.compare(key, a[base + m]) > 0) - lastOfs = m + 1; // a[base + m] < key - else - ofs = m; // key <= a[base + m] - } - assert lastOfs == ofs; // so a[base + ofs - 1] < key <= a[base + ofs] - return ofs; + private static boolean shouldAggressiveMerge(List runs, int newPower) { + return runs.size() >= 2 && runs.get(runs.size() - 1)[2] >= newPower; } - /** - * Like gallopLeft, except that if the range contains an element equal to - * key, gallopRight returns the index after the rightmost equal element. - * - * @param key the key whose insertion point to search for - * @param a the array in which to search - * @param base the index of the first element in the range - * @param len the length of the range; must be > 0 - * @param hint the index at which to begin the search, 0 <= hint < n. - * The closer hint is to the result, the faster this method will run. - * @param c the comparator used to order the range, and to search - * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] - */ - private static int gallopRight(T key, T[] a, int base, int len, - int hint, Comparator c) { - assert len > 0 && hint >= 0 && hint < len; + static void mergeOptimized(T[] a, List runs, Comparator c) { + if (runs.size() < 2) return; - int ofs = 1; - int lastOfs = 0; - if (c.compare(key, a[base + hint]) < 0) { - // Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs] - int maxOfs = hint + 1; - while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) < 0) { - lastOfs = ofs; - ofs = (ofs << 1) + 1; - if (ofs <= 0) // int overflow - ofs = maxOfs; - } - if (ofs > maxOfs) - ofs = maxOfs; + int[] Y = runs.get(runs.size() - 2); + int[] Z = runs.getLast(); - // Make offsets relative to b - int tmp = lastOfs; - lastOfs = hint - ofs; - ofs = hint - tmp; - } else { // a[b + hint] <= key - // Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs] - int maxOfs = len - hint; - while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) >= 0) { - lastOfs = ofs; - ofs = (ofs << 1) + 1; - if (ofs <= 0) // int overflow - ofs = maxOfs; - } - if (ofs > maxOfs) - ofs = maxOfs; + mergeInplaceFast(a, Y[0], Z[0], Z[0] + Z[1], c); - // Make offsets relative to b - lastOfs += hint; - ofs += hint; - } - assert -1 <= lastOfs && lastOfs < ofs && ofs <= len; - - /* - * Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to - * the right of lastOfs but no farther right than ofs. Do a binary - * search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs]. - */ - lastOfs++; - while (lastOfs < ofs) { - int m = lastOfs + ((ofs - lastOfs) >>> 1); - - if (c.compare(key, a[base + m]) < 0) - ofs = m; // key < a[b + m] - else - lastOfs = m + 1; // a[b + m] <= key - } - assert lastOfs == ofs; // so a[b + ofs - 1] <= key < a[b + ofs] - return ofs; + runs.set(runs.size() - 2, new int[]{Y[0], Y[1] + Z[1], Y[2]}); + runs.removeLast(); } - /** - * Merges two adjacent runs in place, in a stable fashion. The first - * element of the first run must be greater than the first element of the - * second run (a[base1] > a[base2]), and the last element of the first run - * (a[base1 + len1-1]) must be greater than all elements of the second run. - * - * For performance, this method should be called only when len1 <= len2; - * its twin, mergeHi should be called if len1 >= len2. (Either method - * may be called if len1 == len2.) - * - * @param base1 index of first element in first run to be merged - * @param len1 length of first run to be merged (must be > 0) - * @param base2 index of first element in second run to be merged - * (must be aBase + aLen) - * @param len2 length of second run to be merged (must be > 0) - */ - private void mergeLo(int base1, int len1, int base2, int len2) { - assert len1 > 0 && len2 > 0 && base1 + len1 == base2; - // Copy first run into temp array - T[] a = this.a; // For performance - T[] tmp = ensureCapacity(len1); - int cursor1 = tmpBase; // Indexes into tmp array - int cursor2 = base2; // Indexes int a - int dest = base1; // Indexes int a - System.arraycopy(a, base1, tmp, cursor1, len1); - // Move first element of second run and deal with degenerate cases - a[dest++] = a[cursor2++]; - if (--len2 == 0) { - System.arraycopy(tmp, cursor1, a, dest, len1); - return; + + + + + + + private static int fastExtendRun(T[] a, int start, Comparator c) { + int j = start + 1; + while (j < a.length && c.compare(a[j-1], a[j]) <= 0) { + j++; } - if (len1 == 1) { - System.arraycopy(a, cursor2, a, dest, len2); - a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge + + if (j < a.length && c.compare(a[j-1], a[j]) > 0) { + reverseRange(a, start, j); + } + return j; + } + + private static int calculateEffectivePower(List runs, int start, int length, int n) { + if (runs.isEmpty()) return 0; + + int[] lastRun = runs.getLast(); + return (start + length/2) / (n/2); + } + + + static void mergeInplaceFast(T[] a, int start, int mid, int end, Comparator c) { + if (end - start <= 16) { + insertionSortRange(a, start, end, c); return; } - Comparator c = this.c; // Use local variable for performance - int minGallop = this.minGallop; // " " " " " - outer: - while (true) { - int count1 = 0; // Number of times in a row that first run won - int count2 = 0; // Number of times in a row that second run won + T[] temp = Arrays.copyOfRange(a, start, end); + int i = 0, j = mid - start, k = start; - /* - * Do the straightforward thing until (if ever) one run starts - * winning consistently. - */ - do { - assert len1 > 1 && len2 > 0; - if (c.compare(a[cursor2], tmp[cursor1]) < 0) { - a[dest++] = a[cursor2++]; - count2++; - count1 = 0; - if (--len2 == 0) - break outer; - } else { - a[dest++] = tmp[cursor1++]; - count1++; - count2 = 0; - if (--len1 == 1) - break outer; - } - } while ((count1 | count2) < minGallop); + while (i < mid - start && j < temp.length) { + if (c.compare(temp[i], temp[j]) <= 0) { + a[k++] = temp[i++]; + } else { + a[k++] = temp[j++]; + } + } - /* - * One run is winning so consistently that galloping may be a - * huge win. So try that, and continue galloping until (if ever) - * neither run appears to be winning consistently anymore. - */ - do { - assert len1 > 1 && len2 > 0; - count1 = gallopRight(a[cursor2], tmp, cursor1, len1, 0, c); - if (count1 != 0) { - System.arraycopy(tmp, cursor1, a, dest, count1); - dest += count1; - cursor1 += count1; - len1 -= count1; - if (len1 <= 1) // len1 == 1 || len1 == 0 - break outer; - } - a[dest++] = a[cursor2++]; - if (--len2 == 0) - break outer; + while (i < mid - start) a[k++] = temp[i++]; + while (j < temp.length) a[k++] = temp[j++]; + } - count2 = gallopLeft(tmp[cursor1], a, cursor2, len2, 0, c); - if (count2 != 0) { - System.arraycopy(a, cursor2, a, dest, count2); - dest += count2; - cursor2 += count2; - len2 -= count2; - if (len2 == 0) - break outer; - } - a[dest++] = tmp[cursor1++]; - if (--len1 == 1) - break outer; - minGallop--; - } while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP); - if (minGallop < 0) - minGallop = 0; - minGallop += 2; // Penalize for leaving gallop mode - } // End of "outer" loop - this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field - - if (len1 == 1) { - assert len2 > 0; - System.arraycopy(a, cursor2, a, dest, len2); - a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge - } else if (len1 == 0) { - throw new IllegalArgumentException( - "Comparison method violates its general contract!"); - } else { - assert len2 == 0; - assert len1 > 1; - System.arraycopy(tmp, cursor1, a, dest, len1); + static void insertionSortRange(T[] a, int start, int end, Comparator c) { + for (int i = start + 1; i < end; i++) { + T key = a[i]; + int j = i - 1; + while (j >= start && c.compare(a[j], key) > 0) { + a[j + 1] = a[j]; + j--; + } + a[j + 1] = key; } } - /** - * Like mergeLo, except that this method should be called only if - * len1 >= len2; mergeLo should be called if len1 <= len2. (Either method - * may be called if len1 == len2.) - * - * @param base1 index of first element in first run to be merged - * @param len1 length of first run to be merged (must be > 0) - * @param base2 index of first element in second run to be merged - * (must be aBase + aLen) - * @param len2 length of second run to be merged (must be > 0) - */ - private void mergeHi(int base1, int len1, int base2, int len2) { - assert len1 > 0 && len2 > 0 && base1 + len1 == base2; - // Copy second run into temp array - T[] a = this.a; // For performance - T[] tmp = ensureCapacity(len2); - int tmpBase = this.tmpBase; - System.arraycopy(a, base2, tmp, tmpBase, len2); - int cursor1 = base1 + len1 - 1; // Indexes into a - int cursor2 = tmpBase + len2 - 1; // Indexes into tmp array - int dest = base2 + len2 - 1; // Indexes into a - // Move last element of first run and deal with degenerate cases - a[dest--] = a[cursor1--]; - if (--len1 == 0) { - System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); - return; - } - if (len2 == 1) { - dest -= len1; - cursor1 -= len1; - System.arraycopy(a, cursor1 + 1, a, dest + 1, len1); - a[dest] = tmp[cursor2]; - return; - } - - Comparator c = this.c; // Use local variable for performance - int minGallop = this.minGallop; // " " " " " - outer: - while (true) { - int count1 = 0; // Number of times in a row that first run won - int count2 = 0; // Number of times in a row that second run won - - /* - * Do the straightforward thing until (if ever) one run - * appears to win consistently. - */ - do { - assert len1 > 0 && len2 > 1; - if (c.compare(tmp[cursor2], a[cursor1]) < 0) { - a[dest--] = a[cursor1--]; - count1++; - count2 = 0; - if (--len1 == 0) - break outer; - } else { - a[dest--] = tmp[cursor2--]; - count2++; - count1 = 0; - if (--len2 == 1) - break outer; - } - } while ((count1 | count2) < minGallop); - - /* - * One run is winning so consistently that galloping may be a - * huge win. So try that, and continue galloping until (if ever) - * neither run appears to be winning consistently anymore. - */ - do { - assert len1 > 0 && len2 > 1; - count1 = len1 - gallopRight(tmp[cursor2], a, base1, len1, len1 - 1, c); - if (count1 != 0) { - dest -= count1; - cursor1 -= count1; - len1 -= count1; - System.arraycopy(a, cursor1 + 1, a, dest + 1, count1); - if (len1 == 0) - break outer; - } - a[dest--] = tmp[cursor2--]; - if (--len2 == 1) - break outer; - - count2 = len2 - gallopLeft(a[cursor1], tmp, tmpBase, len2, len2 - 1, c); - if (count2 != 0) { - dest -= count2; - cursor2 -= count2; - len2 -= count2; - System.arraycopy(tmp, cursor2 + 1, a, dest + 1, count2); - if (len2 <= 1) // len2 == 1 || len2 == 0 - break outer; - } - a[dest--] = a[cursor1--]; - if (--len1 == 0) - break outer; - minGallop--; - } while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP); - if (minGallop < 0) - minGallop = 0; - minGallop += 2; // Penalize for leaving gallop mode - } // End of "outer" loop - this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field - - if (len2 == 1) { - assert len1 > 0; - dest -= len1; - cursor1 -= len1; - System.arraycopy(a, cursor1 + 1, a, dest + 1, len1); - a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge - } else if (len2 == 0) { - throw new IllegalArgumentException( - "Comparison method violates its general contract!"); - } else { - assert len1 == 0; - assert len2 > 0; - System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); + + + + + + + + + + + + + private static int calculateOptimizedPower(List runs, int start, int length, int n) { + if (runs.isEmpty()) return 0; + + int[] lastRun = runs.getLast(); + int l = 0; + double a = (start + length / 2.0) / n; + double b = (lastRun[0] + lastRun[1] / 2.0) / n; + + while (Math.floor(a * Math.pow(2, l)) == Math.floor(b * Math.pow(2, l))) { + l++; } + return l; } - /** - * Ensures that the external array tmp has at least the specified - * number of elements, increasing its size if necessary. The size - * increases exponentially to ensure amortized linear time complexity. - * - * @param minCapacity the minimum required capacity of the tmp array - * @return tmp, whether or not it grew - */ - private T[] ensureCapacity(int minCapacity) { - if (tmpLen < minCapacity) { - // Compute smallest power of 2 > minCapacity - int newSize = -1 >>> Integer.numberOfLeadingZeros(minCapacity); - newSize++; - if (newSize < 0) // Not bloody likely! - newSize = minCapacity; - else - newSize = Math.min(newSize, a.length >>> 1); + static void mergeTopmost2(T[] a, List runs, Comparator c) { + int[] Y = runs.get(runs.size() - 2); + int[] Z = runs.getLast(); - @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) - T[] newArray = (T[])java.lang.reflect.Array.newInstance - (a.getClass().getComponentType(), newSize); - tmp = newArray; - tmpLen = newSize; - tmpBase = 0; + mergeInplace(a, Y[0], Z[0], Z[0] + Z[1], c); + + runs.set(runs.size() - 2, new int[]{Y[0], Y[1] + Z[1], Y[2]}); + runs.removeLast(); + } + public static int extendRunIncreasingOnly(List a, int i) { + if (i == a.size() - 1) { + return i + 1; } - return tmp; + int j = i + 1; + while (j < a.size() && a.get(j - 1) <= a.get(j)) { + j++; + } + return j; + } + + public static int powerFast(int[] run1, int[] run2, int n) { + // Bitwise optimizations + int a = (run1[0] << 1) + run1[1]; + int b = a + run1[1] + run2[1]; + + int l = 0; + while (a < n) { + a <<= 1; + b <<= 1; + l++; + if (b >= n) break; + } + return l; } } \ No newline at end of file diff --git a/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_3.java b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_3.java new file mode 100644 index 0000000..dd9b6d2 --- /dev/null +++ b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_3.java @@ -0,0 +1,178 @@ +package de.uni_marburg.powersort.MSort; + +import java.util.Arrays; +import java.util.Comparator; + +public class IMPL_M_3 { + + private static final int MIN_MERGE = 32; + private static final int MIN_GALLOP = 7; + + private IMPL_M_3() { + } + + public static void fillWithAscRunsHighToLow(Integer[] A, int[] runLengths, int runLenFactor) { + int n = A.length; + assert Arrays.stream(runLengths).sum() * runLenFactor == n; + + for (int i = 0; i < n; i++) { + A[i] = n - i; + } + + int startIndex = 0; + for (int l : runLengths) { + int L = l * runLenFactor; + Arrays.sort(A, startIndex, startIndex + L); + startIndex += L; + } + } + + private static int extendRun(T[] a, int i, Comparator c) { + if (i >= a.length - 1) { + return a.length; // Return the end of the array + } + + int j = i + 1; + boolean ascending = c.compare(a[i], a[j]) <= 0; + + while (j < a.length && c.compare(a[j - 1], a[j]) == (ascending ? -1 : 1)) { + j++; + } + + if (!ascending) { + reverseRange(a, i, j); + } + + return j; + } + + private static void reverseRange(T[] a, int start, int end) { + end--; + while (start < end) { + T temp = a[start]; + a[start++] = a[end]; + a[end--] = temp; + } + } + + private static void mergeInplace(T[] a, int i, int m, int j, Comparator c, T[] temp) { + int leftSize = m - i; + int rightSize = j - m; + + // Validate indices + if (leftSize < 0 || rightSize < 0) { + throw new IllegalArgumentException("Invalid indices: leftSize=" + leftSize + ", rightSize=" + rightSize); + } + if (leftSize < 0) { + throw new IllegalArgumentException("Invalid indices: leftSize is negative"); + } + // Ensure the temporary array is large enough + if (temp.length < leftSize) { + temp = Arrays.copyOf(temp, leftSize); + } + + System.arraycopy(a, i, temp, 0, leftSize); + + int li = 0, ri = m, k = i; + int gallopCount = 0; + + while (li < leftSize && ri < j) { + if (c.compare(temp[li], a[ri]) <= 0) { + a[k++] = temp[li++]; + gallopCount++; + } else { + a[k++] = a[ri++]; + gallopCount = 0; + } + + if (gallopCount >= MIN_GALLOP) { + gallopCount = 0; + while (li < leftSize && ri < j) { + if (c.compare(temp[li], a[ri]) <= 0) { + a[k++] = temp[li++]; + } else { + a[k++] = a[ri++]; + } + } + break; + } + } + + while (li < leftSize) a[k++] = temp[li++]; + while (ri < j) a[k++] = a[ri++]; + } + + public static void powerSort(T[] a, Comparator c) { + int n = a.length; + if (n < MIN_MERGE) { + Arrays.sort(a, c); + return; + } + + // Initialize temporary array with a reasonable size + T[] temp = (T[]) new Object[Math.min(n, MIN_MERGE)]; + int[] runStack = new int[40]; + int stackSize = 0; + + int i = 0; + while (i < n) { + int j = extendRun(a, i, c); + + // Ensure j > i + if (j <= i) { + throw new IllegalStateException("Invalid run: j <= i, i=" + i + ", j=" + j); + } + + int[] newRun = new int[]{i, j - i}; + + // Validate new run + if (newRun[0] >= newRun[1]) { + throw new IllegalArgumentException("Invalid run: start index >= length, i=" + i + ", j=" + j); + } + + i = j; + + if (stackSize > 0) { + int[] prevRun = new int[]{runStack[stackSize - 2], runStack[stackSize - 1]}; + int p = power(prevRun, newRun, n); + + while (stackSize > 0 && p <= runStack[stackSize - 1]) { + mergeInplace(a, runStack[stackSize - 2], runStack[stackSize - 1], i, c, temp); + stackSize -= 2; + } + } + + runStack[stackSize++] = newRun[0]; + runStack[stackSize++] = newRun[1]; + } + + while (stackSize > 2) { + mergeInplace(a, runStack[stackSize - 4], runStack[stackSize - 3], n, c, temp); + stackSize -= 2; + } + } + + private static int power(int[] run1, int[] run2, int n) { + int i1 = run1[0], n1 = run1[1]; + int i2 = run2[0], n2 = run2[1]; + + int a = 2 * i1 + n1; + int b = a + n1 + n2; + + int l = 0; + while (true) { + l++; + if (a >= n) { + a -= n; + b -= n; + } else if (b >= n) { + break; + } + a <<= 1; + b <<= 1; + } + return l; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_4.java b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_4.java new file mode 100644 index 0000000..0006baf --- /dev/null +++ b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_4.java @@ -0,0 +1,1018 @@ +package de.uni_marburg.powersort.MSort; + +import java.util.Comparator; + + +/* + * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright 2009 Google Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * Imported from OpenJDK git repo TimSort.java + */ + + +/** + * A stable, adaptive, iterative mergesort that requires far fewer than + * n lg(n) comparisons when running on partially sorted arrays, while + * offering performance comparable to a traditional mergesort when run + * on random arrays. Like all proper mergesorts, this sort is stable and + * runs O(n log n) time (worst case). In the worst case, this sort requires + * temporary storage space for n/2 object references; in the best case, + * it requires only a small constant amount of space. + * + * This implementation was adapted from Tim Peters's list sort for + * Python, which is described in detail here: + * + * http://svn.python.org/projects/python/trunk/Objects/listsort.txt + * + * Tim's C code may be found here: + * + * http://svn.python.org/projects/python/trunk/Objects/listobject.c + * + * The underlying techniques are described in this paper (and may have + * even earlier origins): + * + * "Optimistic Sorting and Information Theoretic Complexity" + * Peter McIlroy + * SODA (Fourth Annual ACM-SIAM Symposium on Discrete Algorithms), + * pp 467-474, Austin, Texas, 25-27 January 1993. + * + * While the API to this class consists solely of static methods, it is + * (privately) instantiable; a TimSort instance holds the state of an ongoing + * sort, assuming the input array is large enough to warrant the full-blown + * TimSort. Small arrays are sorted in place, using a binary insertion sort. + * + * @author Josh Bloch + */ +public class IMPL_M_4 { + + /** + * This is the minimum sized sequence that will be merged. Shorter + * sequences will be lengthened by calling binarySort. If the entire + * array is less than this length, no merges will be performed. + * + * This constant should be a power of two. It was 64 in Tim Peter's C + * implementation, but 32 was empirically determined to work better in + * this implementation. In the unlikely event that you set this constant + * to be a number that's not a power of two, you'll need to change the + * {@link #minRunLength} computation. + * + * If you decrease this constant, you must change the stackLen + * computation in the TimSort constructor, or you risk an + * ArrayOutOfBounds exception. See listsort.txt for a discussion + * of the minimum stack length required as a function of the length + * of the array being sorted and the minimum merge sequence length. + */ + private static final int MIN_MERGE = 32; + + + /** + * The array being sorted. + */ + private final T[] a; + + /** + * The comparator for this sort. + */ + private final Comparator c; + + /** + * When we get into galloping mode, we stay there until both runs win less + * often than MIN_GALLOP consecutive times. + */ + private static final int MIN_GALLOP = 7; + + /** + * This controls when we get *into* galloping mode. It is initialized + * to MIN_GALLOP. The mergeLo and mergeHi methods nudge it higher for + * random data, and lower for highly structured data. + */ + private int minGallop = MIN_GALLOP; + + /** + * Maximum initial size of tmp array, which is used for merging. The array + * can grow to accommodate demand. + * + * Unlike Tim's original C version, we do not allocate this much storage + * when sorting smaller arrays. This change was required for performance. + */ + private static final int INITIAL_TMP_STORAGE_LENGTH = 256; + + /** + * Temp storage for merges. A workspace array may optionally be + * provided in constructor, and if so will be used as long as it + * is big enough. + */ + private T[] tmp; + private int tmpBase; // base of tmp array slice + private int tmpLen; // length of tmp array slice + + /** + * A stack of pending runs yet to be merged. Run i starts at + * address base[i] and extends for len[i] elements. It's always + * true (so long as the indices are in bounds) that: + * + * runBase[i] + runLen[i] == runBase[i + 1] + * + * so we could cut the storage for this, but it's a minor amount, + * and keeping all the info explicit simplifies the code. + */ + private int stackSize = 0; // Number of pending runs on stack + private final int[] runBase; + private final int[] runLen; + + /** + * Creates a TimSort instance to maintain the state of an ongoing sort. + * + * @param a the array to be sorted + * @param c the comparator to determine the order of the sort + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array + */ + + + /** + MINE + **/ + + + private final int[] runPower; // Added to track power of each run + + + + + + + + private IMPL_M_4(T[] a, Comparator c, T[] work, int workBase, int workLen) { + this.a = a; + this.c = c; + + + // Allocate temp storage (which may be increased later if necessary) + int len = a.length; + int tlen = (len < 2 * INITIAL_TMP_STORAGE_LENGTH) ? + len >>> 1 : INITIAL_TMP_STORAGE_LENGTH; + if (work == null || workLen < tlen || workBase + tlen > work.length) { + @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) + T[] newArray = (T[])java.lang.reflect.Array.newInstance + (a.getClass().getComponentType(), tlen); + tmp = newArray; + tmpBase = 0; + tmpLen = tlen; + } + else { + tmp = work; + tmpBase = workBase; + tmpLen = workLen; + } + + /* + * Allocate runs-to-be-merged stack (which cannot be expanded). The + * stack length requirements are described in listsort.txt. The C + * version always uses the same stack length (85), but this was + * measured to be too expensive when sorting "mid-sized" arrays (e.g., + * 100 elements) in Java. Therefore, we use smaller (but sufficiently + * large) stack lengths for smaller arrays. The "magic numbers" in the + * computation below must be changed if MIN_MERGE is decreased. See + * the MIN_MERGE declaration above for more information. + * The maximum value of 49 allows for an array up to length + * Integer.MAX_VALUE-4, if array is filled by the worst case stack size + * increasing scenario. More explanations are given in section 4 of: + * http://envisage-project.eu/wp-content/uploads/2015/02/sorting.pdf + */ + int stackLen = (len < 120 ? 5 : + len < 1542 ? 10 : + len < 119151 ? 24 : 49); + runBase = new int[stackLen]; + runLen = new int[stackLen]; + runPower = new int[stackLen]; + } + + /* + * The next method (package private and static) constitutes the + * entire API of this class. + */ + + /** + * Sorts the given range, using the given workspace array slice + * for temp storage when possible. This method is designed to be + * invoked from public methods (in class Arrays) after performing + * any necessary array bounds checks and expanding parameters into + * the required forms. + * + * @param a the array to be sorted + * @param lo the index of the first element, inclusive, to be sorted + * @param hi the index of the last element, exclusive, to be sorted + * @param c the comparator to use + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array + * @since 1.8 + */ + public static void sort(T[] a, int lo, int hi, Comparator c, + T[] work, int workBase, int workLen) { + assert c != null && a != null && lo >= 0 && lo <= hi && hi <= a.length; + + int nRemaining = hi - lo; + if (nRemaining < 2) + return; // Arrays of size 0 and 1 are always sorted + + // If array is small, do a "mini-TimSort" with no merges + if (nRemaining < MIN_MERGE) { + int initRunLen = countRunAndMakeAscending(a, lo, hi, c); + binarySort(a, lo, hi, lo + initRunLen, c); + return; + } + + /** + * March over the array once, left to right, finding natural runs, + * extending short natural runs to minRun elements, and merging runs + * to maintain stack invariant. + */ + IMPL_M_4 ts = new IMPL_M_4<>(a, c, work, workBase, workLen); + int minRun = minRunLength(nRemaining); + do { + // Identify next run + int runLen = countRunAndMakeAscending(a, lo, hi, c); + + // Extend run if necessary + if (runLen < minRun) { + int force = Math.min(nRemaining, minRun); + binarySort(a, lo, lo + force, lo + runLen, c); + runLen = force; + } + + // Push run onto pending stack + ts.pushRun(lo, runLen,0); + + // Ensure `stackSize` is valid before merging + if (ts.stackSize > 1) { + ts.mergeCollapse(); // Only merge if there are enough runs + } + + // Move to the next segment + lo += runLen; + nRemaining -= runLen; + } while (nRemaining != 0); + + // Final merging phase + assert lo == hi; + if (ts.stackSize > 1) { + ts.mergeForceCollapse(); + } + assert ts.stackSize == 1; + } + + /** + * Sorts the specified portion of the specified array using a binary + * insertion sort. This is the best method for sorting small numbers + * of elements. It requires O(n log n) compares, but O(n^2) data + * movement (worst case). + * + * If the initial part of the specified range is already sorted, + * this method can take advantage of it: the method assumes that the + * elements from index {@code lo}, inclusive, to {@code start}, + * exclusive are already sorted. + * + * @param a the array in which a range is to be sorted + * @param lo the index of the first element in the range to be sorted + * @param hi the index after the last element in the range to be sorted + * @param start the index of the first element in the range that is + * not already known to be sorted ({@code lo <= start <= hi}) + * @param c comparator to used for the sort + */ + @SuppressWarnings("fallthrough") + private static void binarySort(T[] a, int lo, int hi, int start, + Comparator c) { + assert lo <= start && start <= hi; + if (start == lo) + start++; + for ( ; start < hi; start++) { + T pivot = a[start]; + + // Set left (and right) to the index where a[start] (pivot) belongs + int left = lo; + int right = start; + assert left <= right; + /* + * Invariants: + * pivot >= all in [lo, left). + * pivot < all in [right, start). + */ + while (left < right) { + int mid = (left + right) >>> 1; + if (c.compare(pivot, a[mid]) < 0) + right = mid; + else + left = mid + 1; + } + assert left == right; + + /* + * The invariants still hold: pivot >= all in [lo, left) and + * pivot < all in [left, start), so pivot belongs at left. Note + * that if there are elements equal to pivot, left points to the + * first slot after them -- that's why this sort is stable. + * Slide elements over to make room for pivot. + */ + int n = start - left; // The number of elements to move + // Switch is just an optimization for arraycopy in default case + switch (n) { + case 2: a[left + 2] = a[left + 1]; + case 1: a[left + 1] = a[left]; + break; + default: System.arraycopy(a, left, a, left + 1, n); + } + a[left] = pivot; + } + } + + /** + * Returns the length of the run beginning at the specified position in + * the specified array and reverses the run if it is descending (ensuring + * that the run will always be ascending when the method returns). + * + * A run is the longest ascending sequence with: + * + * a[lo] <= a[lo + 1] <= a[lo + 2] <= ... + * + * or the longest descending sequence with: + * + * a[lo] > a[lo + 1] > a[lo + 2] > ... + * + * For its intended use in a stable mergesort, the strictness of the + * definition of "descending" is needed so that the call can safely + * reverse a descending sequence without violating stability. + * + * @param a the array in which a run is to be counted and possibly reversed + * @param lo index of the first element in the run + * @param hi index after the last element that may be contained in the run. + * It is required that {@code lo < hi}. + * @param c the comparator to used for the sort + * @return the length of the run beginning at the specified position in + * the specified array + */ + private static int countRunAndMakeAscending(T[] a, int lo, int hi, + Comparator c) { + assert lo < hi; + int runHi = lo + 1; + if (runHi == hi) + return 1; + + // Find end of run, and reverse range if descending + if (c.compare(a[runHi++], a[lo]) < 0) { // Descending + while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0) + runHi++; + reverseRange(a, lo, runHi); + } else { // Ascending + while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0) + runHi++; + } + + return runHi - lo; + } + + /** + * Reverse the specified range of the specified array. + * + * @param a the array in which a range is to be reversed + * @param lo the index of the first element in the range to be reversed + * @param hi the index after the last element in the range to be reversed + */ + private static void reverseRange(Object[] a, int lo, int hi) { + hi--; + while (lo < hi) { + Object t = a[lo]; + a[lo++] = a[hi]; + a[hi--] = t; + } + } + + /** + * Returns the minimum acceptable run length for an array of the specified + * length. Natural runs shorter than this will be extended with + * {@link #binarySort}. + * + * Roughly speaking, the computation is: + * + * If n < MIN_MERGE, return n (it's too small to bother with fancy stuff). + * Else if n is an exact power of 2, return MIN_MERGE/2. + * Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k + * is close to, but strictly less than, an exact power of 2. + * + * For the rationale, see listsort.txt. + * + * @param n the length of the array to be sorted + * @return the length of the minimum run to be merged + */ + private static int minRunLength(int n) { + assert n >= 0; + int r = 0; // Becomes 1 if any 1 bits are shifted off + while (n >= MIN_MERGE) { + r |= (n & 1); + n >>= 1; + } + return n + r; + } + + /** + * Pushes the specified run onto the pending-run stack. + * + * @param runBase index of the first element in the run + * @param runLen the number of elements in the run + */ + private void pushRun(int runBase, int runLen, int power) { + this.runBase[stackSize] = runBase; + this.runLen[stackSize] = runLen; + this.runPower[stackSize] = power; + stackSize++; + } + + private int computePower(int i1, int j1, int i2, int j2, int n) { + int n1 = j1 - i1; + int n2 = j2 - i2; + double a = (i1 + 0.5 * (n1 - 1)) / n; + double b = (i2 + 0.5 * (n2 - 1)) / n; + int l = 0; + while (Math.floor(a) == Math.floor(b)) { + a = (a - Math.floor(a)) * 2; + b = (b - Math.floor(b)) * 2; + l++; + } + return l; + } + + /** + * Examines the stack of runs waiting to be merged and merges adjacent runs + * until the stack invariants are reestablished: + * + * 1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1] + * 2. runLen[i - 2] > runLen[i - 1] + * + * This method is called each time a new run is pushed onto the stack, + * so the invariants are guaranteed to hold for i < stackSize upon + * entry to the method. + * + * Thanks to Stijn de Gouw, Jurriaan Rot, Frank S. de Boer, + * Richard Bubel and Reiner Hahnle, this is fixed with respect to + * the analysis in "On the Worst-Case Complexity of TimSort" by + * Nicolas Auger, Vincent Jug, Cyril Nicaud, and Carine Pivoteau. + */ + private void mergeCollapse() { + while (stackSize > 1) { + int n = stackSize - 2; + + if (n > 0 && n + 1 < stackSize && runLen[n - 1] <= runLen[n] + runLen[n + 1]) { + if (runLen[n - 1] < runLen[n + 1]) { + n--; + } + } else if (n < 0 || n + 1 >= stackSize || runLen[n] > runLen[n + 1]) { + break; // Ensure valid index + } + + if (n >= 0 && n < stackSize - 1) { // Prevent -1 index + mergeAt(n); + } + } + } + + + /** + * Merges all runs on the stack until only one remains. This method is + * called once, to complete the sort. + */ + private void mergeForceCollapse() { + while (stackSize > 1) { + int n = stackSize - 2; + + // Ensure `n` is never negative + if (n < 0) { + System.err.println("ERROR: mergeForceCollapse attempted with negative index!"); + return; // Prevents calling mergeAt(-1) + } + + if (n > 0 && runLen[n - 1] < runLen[n + 1]) { + n--; + } + + if (n >= 0 && n < stackSize - 1) { + mergeAt(n); + } else { + System.err.println("ERROR: mergeAt(n) called with invalid index: " + n); + } + } + } + + + + /** + * Merges the two runs at stack indices i and i+1. Run i must be + * the penultimate or antepenultimate run on the stack. In other words, + * i must be equal to stackSize-2 or stackSize-3. + * + * @param i stack index of the first of the two runs to merge + */ + private void mergeAt(int i) { + assert stackSize >= 2; + assert i >= 0; + assert i == stackSize - 2 || i == stackSize - 3; + + int base1 = runBase[i]; + int len1 = runLen[i]; + int base2 = runBase[i + 1]; + int len2 = runLen[i + 1]; + assert len1 > 0 && len2 > 0; + assert base1 + len1 == base2; + +// if (i < 0 || i >= stackSize - 1) { +// throw new IllegalStateException("mergeAt called with invalid index: " + i); +// } +// System.out.println("Merging at index: " + i + " with stackSize: " + stackSize); + + /* + * Record the length of the combined runs; if i is the 3rd-last + * run now, also slide over the last run (which isn't involved + * in this merge). The current run (i+1) goes away in any case. + */ + runLen[i] = len1 + len2; + + // Update runPower before modifying the stack structure + runPower[i] = runPower[i + 1]; // Transfer power from the absorbed run + + if (i == stackSize - 3) { + runBase[i + 1] = runBase[i + 2]; + runLen[i + 1] = runLen[i + 2]; + runPower[i + 1] = runPower[i + 2]; // Also slide the power value + } + stackSize--; + + /* + * Find where the first element of run2 goes in run1. Prior elements + * in run1 can be ignored (because they're already in place). + */ + int k = gallopRight(a[base2], a, base1, len1, 0, c); + assert k >= 0; + base1 += k; + len1 -= k; + if (len1 == 0) + return; + + /* + * Find where the last element of run1 goes in run2. Subsequent elements + * in run2 can be ignored (because they're already in place). + */ + len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c); + assert len2 >= 0; + if (len2 == 0) + return; + + // Merge remaining runs, using tmp array with min(len1, len2) elements + if (len1 <= len2) + mergeLo(base1, len1, base2, len2); + else + mergeHi(base1, len1, base2, len2); + } + + /** + * Locates the position at which to insert the specified key into the + * specified sorted range; if the range contains an element equal to key, + * returns the index of the leftmost equal element. + * + * @param key the key whose insertion point to search for + * @param a the array in which to search + * @param base the index of the first element in the range + * @param len the length of the range; must be > 0 + * @param hint the index at which to begin the search, 0 <= hint < n. + * The closer hint is to the result, the faster this method will run. + * @param c the comparator used to order the range, and to search + * @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], + * pretending that a[b - 1] is minus infinity and a[b + n] is infinity. + * In other words, key belongs at index b + k; or in other words, + * the first k elements of a should precede key, and the last n - k + * should follow it. + */ + private static int gallopLeft(T key, T[] a, int base, int len, int hint, + Comparator c) { + assert len > 0 && hint >= 0 && hint < len; + int lastOfs = 0; + int ofs = 1; + if (c.compare(key, a[base + hint]) > 0) { + // Gallop right until a[base+hint+lastOfs] < key <= a[base+hint+ofs] + int maxOfs = len - hint; + while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) > 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) // int overflow + ofs = maxOfs; + } + if (ofs > maxOfs) + ofs = maxOfs; + + // Make offsets relative to base + lastOfs += hint; + ofs += hint; + } else { // key <= a[base + hint] + // Gallop left until a[base+hint-ofs] < key <= a[base+hint-lastOfs] + final int maxOfs = hint + 1; + while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) <= 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) // int overflow + ofs = maxOfs; + } + if (ofs > maxOfs) + ofs = maxOfs; + + // Make offsets relative to base + int tmp = lastOfs; + lastOfs = hint - ofs; + ofs = hint - tmp; + } + assert -1 <= lastOfs && lastOfs < ofs && ofs <= len; + + /* + * Now a[base+lastOfs] < key <= a[base+ofs], so key belongs somewhere + * to the right of lastOfs but no farther right than ofs. Do a binary + * search, with invariant a[base + lastOfs - 1] < key <= a[base + ofs]. + */ + lastOfs++; + while (lastOfs < ofs) { + int m = lastOfs + ((ofs - lastOfs) >>> 1); + + if (c.compare(key, a[base + m]) > 0) + lastOfs = m + 1; // a[base + m] < key + else + ofs = m; // key <= a[base + m] + } + assert lastOfs == ofs; // so a[base + ofs - 1] < key <= a[base + ofs] + return ofs; + } + + /** + * Like gallopLeft, except that if the range contains an element equal to + * key, gallopRight returns the index after the rightmost equal element. + * + * @param key the key whose insertion point to search for + * @param a the array in which to search + * @param base the index of the first element in the range + * @param len the length of the range; must be > 0 + * @param hint the index at which to begin the search, 0 <= hint < n. + * The closer hint is to the result, the faster this method will run. + * @param c the comparator used to order the range, and to search + * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] + */ + private static int gallopRight(T key, T[] a, int base, int len, + int hint, Comparator c) { + assert len > 0 && hint >= 0 && hint < len; + + int ofs = 1; + int lastOfs = 0; + if (c.compare(key, a[base + hint]) < 0) { + // Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs] + int maxOfs = hint + 1; + while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) < 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) // int overflow + ofs = maxOfs; + } + if (ofs > maxOfs) + ofs = maxOfs; + + // Make offsets relative to b + int tmp = lastOfs; + lastOfs = hint - ofs; + ofs = hint - tmp; + } else { // a[b + hint] <= key + // Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs] + int maxOfs = len - hint; + while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) >= 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) // int overflow + ofs = maxOfs; + } + if (ofs > maxOfs) + ofs = maxOfs; + + // Make offsets relative to b + lastOfs += hint; + ofs += hint; + } + assert -1 <= lastOfs && lastOfs < ofs && ofs <= len; + + /* + * Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to + * the right of lastOfs but no farther right than ofs. Do a binary + * search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs]. + */ + lastOfs++; + while (lastOfs < ofs) { + int m = lastOfs + ((ofs - lastOfs) >>> 1); + + if (c.compare(key, a[base + m]) < 0) + ofs = m; // key < a[b + m] + else + lastOfs = m + 1; // a[b + m] <= key + } + assert lastOfs == ofs; // so a[b + ofs - 1] <= key < a[b + ofs] + return ofs; + } + + /** + * Merges two adjacent runs in place, in a stable fashion. The first + * element of the first run must be greater than the first element of the + * second run (a[base1] > a[base2]), and the last element of the first run + * (a[base1 + len1-1]) must be greater than all elements of the second run. + * + * For performance, this method should be called only when len1 <= len2; + * its twin, mergeHi should be called if len1 >= len2. (Either method + * may be called if len1 == len2.) + * + * @param base1 index of first element in first run to be merged + * @param len1 length of first run to be merged (must be > 0) + * @param base2 index of first element in second run to be merged + * (must be aBase + aLen) + * @param len2 length of second run to be merged (must be > 0) + */ + private void mergeLo(int base1, int len1, int base2, int len2) { + assert len1 > 0 && len2 > 0 && base1 + len1 == base2; + + // Copy first run into temp array + T[] a = this.a; // For performance + T[] tmp = ensureCapacity(len1); + int cursor1 = tmpBase; // Indexes into tmp array + int cursor2 = base2; // Indexes int a + int dest = base1; // Indexes int a + System.arraycopy(a, base1, tmp, cursor1, len1); + + // Move first element of second run and deal with degenerate cases + a[dest++] = a[cursor2++]; + if (--len2 == 0) { + System.arraycopy(tmp, cursor1, a, dest, len1); + return; + } + if (len1 == 1) { + System.arraycopy(a, cursor2, a, dest, len2); + a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge + return; + } + + Comparator c = this.c; // Use local variable for performance + int minGallop = this.minGallop; // " " " " " + outer: + while (true) { + int count1 = 0; // Number of times in a row that first run won + int count2 = 0; // Number of times in a row that second run won + + /* + * Do the straightforward thing until (if ever) one run starts + * winning consistently. + */ + do { + assert len1 > 1 && len2 > 0; + if (c.compare(a[cursor2], tmp[cursor1]) < 0) { + a[dest++] = a[cursor2++]; + count2++; + count1 = 0; + if (--len2 == 0) + break outer; + } else { + a[dest++] = tmp[cursor1++]; + count1++; + count2 = 0; + if (--len1 == 1) + break outer; + } + } while ((count1 | count2) < minGallop); + + /* + * One run is winning so consistently that galloping may be a + * huge win. So try that, and continue galloping until (if ever) + * neither run appears to be winning consistently anymore. + */ + do { + assert len1 > 1 && len2 > 0; + count1 = gallopRight(a[cursor2], tmp, cursor1, len1, 0, c); + if (count1 != 0) { + System.arraycopy(tmp, cursor1, a, dest, count1); + dest += count1; + cursor1 += count1; + len1 -= count1; + if (len1 <= 1) // len1 == 1 || len1 == 0 + break outer; + } + a[dest++] = a[cursor2++]; + if (--len2 == 0) + break outer; + + count2 = gallopLeft(tmp[cursor1], a, cursor2, len2, 0, c); + if (count2 != 0) { + System.arraycopy(a, cursor2, a, dest, count2); + dest += count2; + cursor2 += count2; + len2 -= count2; + if (len2 == 0) + break outer; + } + a[dest++] = tmp[cursor1++]; + if (--len1 == 1) + break outer; + minGallop--; + } while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP); + if (minGallop < 0) + minGallop = 0; + minGallop += 2; // Penalize for leaving gallop mode + } // End of "outer" loop + this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field + + if (len1 == 1) { + assert len2 > 0; + System.arraycopy(a, cursor2, a, dest, len2); + a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge + } else if (len1 == 0) { + throw new IllegalArgumentException( + "Comparison method violates its general contract!"); + } else { + assert len2 == 0; + assert len1 > 1; + System.arraycopy(tmp, cursor1, a, dest, len1); + } + } + + /** + * Like mergeLo, except that this method should be called only if + * len1 >= len2; mergeLo should be called if len1 <= len2. (Either method + * may be called if len1 == len2.) + * + * @param base1 index of first element in first run to be merged + * @param len1 length of first run to be merged (must be > 0) + * @param base2 index of first element in second run to be merged + * (must be aBase + aLen) + * @param len2 length of second run to be merged (must be > 0) + */ + private void mergeHi(int base1, int len1, int base2, int len2) { + assert len1 > 0 && len2 > 0 && base1 + len1 == base2; + + // Copy second run into temp array + T[] a = this.a; // For performance + T[] tmp = ensureCapacity(len2); + int tmpBase = this.tmpBase; + System.arraycopy(a, base2, tmp, tmpBase, len2); + + int cursor1 = base1 + len1 - 1; // Indexes into a + int cursor2 = tmpBase + len2 - 1; // Indexes into tmp array + int dest = base2 + len2 - 1; // Indexes into a + + // Move last element of first run and deal with degenerate cases + a[dest--] = a[cursor1--]; + if (--len1 == 0) { + System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); + return; + } + if (len2 == 1) { + dest -= len1; + cursor1 -= len1; + System.arraycopy(a, cursor1 + 1, a, dest + 1, len1); + a[dest] = tmp[cursor2]; + return; + } + + Comparator c = this.c; // Use local variable for performance + int minGallop = this.minGallop; // " " " " " + outer: + while (true) { + int count1 = 0; // Number of times in a row that first run won + int count2 = 0; // Number of times in a row that second run won + + /* + * Do the straightforward thing until (if ever) one run + * appears to win consistently. + */ + do { + assert len1 > 0 && len2 > 1; + if (c.compare(tmp[cursor2], a[cursor1]) < 0) { + a[dest--] = a[cursor1--]; + count1++; + count2 = 0; + if (--len1 == 0) + break outer; + } else { + a[dest--] = tmp[cursor2--]; + count2++; + count1 = 0; + if (--len2 == 1) + break outer; + } + } while ((count1 | count2) < minGallop); + + /* + * One run is winning so consistently that galloping may be a + * huge win. So try that, and continue galloping until (if ever) + * neither run appears to be winning consistently anymore. + */ + do { + assert len1 > 0 && len2 > 1; + count1 = len1 - gallopRight(tmp[cursor2], a, base1, len1, len1 - 1, c); + if (count1 != 0) { + dest -= count1; + cursor1 -= count1; + len1 -= count1; + System.arraycopy(a, cursor1 + 1, a, dest + 1, count1); + if (len1 == 0) + break outer; + } + a[dest--] = tmp[cursor2--]; + if (--len2 == 1) + break outer; + + count2 = len2 - gallopLeft(a[cursor1], tmp, tmpBase, len2, len2 - 1, c); + if (count2 != 0) { + dest -= count2; + cursor2 -= count2; + len2 -= count2; + System.arraycopy(tmp, cursor2 + 1, a, dest + 1, count2); + if (len2 <= 1) // len2 == 1 || len2 == 0 + break outer; + } + a[dest--] = a[cursor1--]; + if (--len1 == 0) + break outer; + minGallop--; + } while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP); + if (minGallop < 0) + minGallop = 0; + minGallop += 2; // Penalize for leaving gallop mode + } // End of "outer" loop + this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field + + if (len2 == 1) { + assert len1 > 0; + dest -= len1; + cursor1 -= len1; + System.arraycopy(a, cursor1 + 1, a, dest + 1, len1); + a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge + } else if (len2 == 0) { + throw new IllegalArgumentException( + "Comparison method violates its general contract!"); + } else { + assert len1 == 0; + assert len2 > 0; + System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); + } + } + + /** + * Ensures that the external array tmp has at least the specified + * number of elements, increasing its size if necessary. The size + * increases exponentially to ensure amortized linear time complexity. + * + * @param minCapacity the minimum required capacity of the tmp array + * @return tmp, whether or not it grew + */ + private T[] ensureCapacity(int minCapacity) { + if (tmpLen < minCapacity) { + // Compute smallest power of 2 > minCapacity + int newSize = -1 >>> Integer.numberOfLeadingZeros(minCapacity); + newSize++; + + if (newSize < 0) // Not bloody likely! + newSize = minCapacity; + else + newSize = Math.min(newSize, a.length >>> 1); + + @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) + T[] newArray = (T[])java.lang.reflect.Array.newInstance + (a.getClass().getComponentType(), newSize); + tmp = newArray; + tmpLen = newSize; + tmpBase = 0; + } + return tmp; + } +} \ No newline at end of file diff --git a/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java b/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java index bac315d..0d8b1b7 100644 --- a/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java +++ b/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java @@ -4,6 +4,8 @@ import de.uni_marburg.powersort.FinnSort.FinnSort; import de.uni_marburg.powersort.FinnSort.FasterFinnSort; import de.uni_marburg.powersort.MSort.IMPL_M_1; import de.uni_marburg.powersort.MSort.IMPL_M_2; +import de.uni_marburg.powersort.MSort.IMPL_M_3; +import de.uni_marburg.powersort.MSort.IMPL_M_4; import de.uni_marburg.powersort.benchmark.NaturalOrder; import de.uni_marburg.powersort.sort.dpqs.DualPivotQuicksort; @@ -14,11 +16,14 @@ public enum SortEnum { FINN_SORT, IMPL_M_10, IMPL_M_20, + IMPL_M_30, + IMPL_M_40, DPQS, QUICK_SORT, MERGE_SORT, BUBBLE_SORT; + public SortImpl getSortImpl() { return switch (this) { case BUBBLE_SORT -> array -> BubbleSort.sort(array, NaturalOrder.INSTANCE); @@ -28,7 +33,9 @@ public enum SortEnum { case TIM_SORT -> array -> TimSort.sort(array, 0, array.length, NaturalOrder.INSTANCE, null, 0, 0); case FINN_SORT -> array -> FinnSort.sort(array, NaturalOrder.INSTANCE); case IMPL_M_10 -> array -> IMPL_M_1.powerSort(array,NaturalOrder.INSTANCE); - case IMPL_M_20 -> array -> IMPL_M_2.sort(array, 0, array.length, NaturalOrder.INSTANCE, null, 0, 0); + case IMPL_M_20 -> array -> IMPL_M_2.powerSort(array,NaturalOrder.INSTANCE); + case IMPL_M_30 -> array -> IMPL_M_3.powerSort(array,NaturalOrder.INSTANCE); + case IMPL_M_40 -> array -> IMPL_M_4.sort(array, 0, array.length, NaturalOrder.INSTANCE, null, 0, 0); case FASTER_FINN_SORT -> array -> FasterFinnSort.sort(array, 0, array.length, NaturalOrder.INSTANCE, null, 0, 0); case ASORT -> array -> ASort.sort(array, NaturalOrder.INSTANCE); }; diff --git a/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortT.java b/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortT.java index 0664717..6624142 100644 --- a/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortT.java +++ b/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortT.java @@ -1,6 +1,6 @@ package de.uni_marburg.powersort.MSort; -import static de.uni_marburg.powersort.MSort.IMPL_M_1.*; +import static de.uni_marburg.powersort.MSort.IMPL_M_3.*; import java.util.ArrayList; import java.util.Arrays; @@ -13,8 +13,8 @@ public class PowerSortT { public static void main(String[] args) { testFillWithAscRunsHighToLow(); - testMerge(); - testMergeInplace(); + //testMerge(); + // testMergeInplace(); testExtendRun(); testPower(); testPowerFast(); @@ -32,25 +32,25 @@ public class PowerSortT { } // Test for merge - public static void testMerge() { - Integer[] run1 ={1,4,6}; - Integer []run2 = {2, 3, 5}; - Integer[] result = merge(run1, run2, NaturalOrder.INSTANCE); - System.out.println("Test merge: " + result); - } +// public static void testMerge() { +// Integer[] run1 ={1,4,6}; +// Integer []run2 = {2, 3, 5}; +// Integer[] result = merge(run1, run2, NaturalOrder.INSTANCE); +// System.out.println("Test merge: " + result); +// } // Test for mergeInplace - public static void testMergeInplace() { - Integer[] A = {1,4,6,2,3,5}; - mergeInplace(A, 0, 3, 6,NaturalOrder.INSTANCE); - System.out.println("Test mergeInplace: " + A); - } +// public static void testMergeInplace() { +// Integer[] A = {1,4,6,2,3,5}; +// mergeInplace(A, 0, 3, 6,NaturalOrder.INSTANCE); +// System.out.println("Test mergeInplace: " + A); +// } // Test for extendRun public static void testExtendRun() { Integer [] A = {1, 2, 3, 6, 5, 4}; - int endIndex = extendRun(A, 0,NaturalOrder.INSTANCE); - System.out.println("Test extendRun (from 0): " + endIndex); + // int endIndex = extendRun(A, 0,NaturalOrder.INSTANCE); + // System.out.println("Test extendRun (from 0): " + endIndex); System.out.println("Modified List: " + A); } @@ -59,8 +59,8 @@ public class PowerSortT { int[] run1 = {0, 3}; int[] run2 = {3, 3}; int n = 6; - int powerValue = power(run1, run2, n); - System.out.println("Test power: " + powerValue); + // int powerValue = power(run1, run2, n); + // System.out.println("Test power: " + powerValue); } // Test for powerFast @@ -68,8 +68,8 @@ public class PowerSortT { int[] run1 = {0, 3}; int[] run2 = {3, 3}; int n = 6; - int powerFastValue = powerFast(run1, run2, n); - System.out.println("Test powerFast: " + powerFastValue); + // int powerFastValue = powerFast(run1, run2, n); + // System.out.println("Test powerFast: " + powerFastValue); } // Test for mergeTopmost2 @@ -78,7 +78,7 @@ public class PowerSortT { List runs = new ArrayList<>(); runs.add(new int[]{0, 3, 1}); runs.add(new int[]{3, 3, 1}); - mergeTopmost2(A, runs,NaturalOrder.INSTANCE); + // mergeTopmost2(A, runs,NaturalOrder.INSTANCE); System.out.println("Test mergeTopmost2: " + A); } diff --git a/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortTest.java b/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortTest.java index 134834e..a806e37 100644 --- a/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortTest.java +++ b/app/src/test/java/de/uni_marburg/powersort/MSort/PowerSortTest.java @@ -9,9 +9,9 @@ import java.util.stream.IntStream; import de.uni_marburg.powersort.benchmark.NaturalOrder; import org.junit.jupiter.api.Test; -import static de.uni_marburg.powersort.MSort.IMPL_M_1.MERGE_COST; -import static de.uni_marburg.powersort.MSort.IMPL_M_1.fillWithAscRunsHighToLow; -import static de.uni_marburg.powersort.MSort.IMPL_M_1.powerSort; +//import static de.uni_marburg.powersort.MSort.IMPL_M_3.MERGE_COST; +import static de.uni_marburg.powersort.MSort.IMPL_M_3.fillWithAscRunsHighToLow; +import static de.uni_marburg.powersort.MSort.IMPL_M_3.powerSort; class PowerSortTest { @Test @@ -28,11 +28,11 @@ class PowerSortTest { System.out.println(); fillWithAscRunsHighToLow(a, runs, 1); - MERGE_COST = 0; + //MERGE_COST = 0; System.out.println("Sorting with Powersort:"); powerSort(a,NaturalOrder.INSTANCE); System.out.println("Sorted Array"+Arrays.toString(a)); - System.out.println("Merge cost: " + MERGE_COST); + // System.out.println("Merge cost: " + MERGE_COST); } @Test From 2e9c2ca2bf2fbd8995198d55fda0b857b102ff86 Mon Sep 17 00:00:00 2001 From: Daniel Langbein Date: Tue, 28 Jan 2025 19:47:12 +0100 Subject: [PATCH 2/6] misc during meeting --- .../de/uni_marburg/powersort/benchmark/JmhBase.java | 2 +- .../de/uni_marburg/powersort/benchmark/JmhCgl.java | 2 +- .../powersort/benchmark/JmhCompetition.java | 13 +++++++++++-- .../powersort/FinnSort/FasterFinnSort.java | 2 +- .../de/uni_marburg/powersort/sort/IMPL_M_1Test.java | 3 +++ .../de/uni_marburg/powersort/sort/IMPL_M_2Test.java | 10 ++++++++++ .../de/uni_marburg/powersort/sort/IMPL_M_3Test.java | 10 ++++++++++ .../de/uni_marburg/powersort/sort/IMPL_M_4Test.java | 7 +++++++ 8 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_2Test.java create mode 100644 app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_3Test.java create mode 100644 app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_4Test.java diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java index 3f59c38..fa72c5a 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java @@ -34,7 +34,7 @@ import java.util.concurrent.TimeUnit; // AverageTime: "Average time per operation." // - "This mode is time-based, and it will run until the iteration time expires." //@BenchmarkMode(Mode.AverageTime) -//@Warmup(iterations = 6, time = 1, timeUnit = TimeUnit.SECONDS) +//@Warmup(iterations = 20, time = 1, timeUnit = TimeUnit.SECONDS) //@Measurement(iterations = 6, time = 1, timeUnit = TimeUnit.SECONDS) // SingleShotTime: "Time per single operation" diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java index dff9de6..fd0973f 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java @@ -23,7 +23,7 @@ public class JmhCgl extends JmhBase { // Either all or a selection of sort implementations. //@Param() - @Param({"TIM_SORT", "FASTER_FINN_SORT", "IMPL_M_11", "IMPL_M_21"}) + @Param({"TIM_SORT", "FASTER_FINN_SORT", "IMPL_M_40"}) SortEnum sortEnum; @Override diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java index 8d77a10..cd0f4eb 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCompetition.java @@ -16,9 +16,18 @@ import org.openjdk.jmh.annotations.State; */ @State(Scope.Benchmark) public class JmhCompetition extends JmhBase { - @Param() + //@Param() + @Param({ + // Top 4 Heavyweight by #comparisons + "COMPETITION_207", "COMPETITION_214", "COMPETITION_213", "COMPETITION_236", + // Top 4 Heavyweight by #merge-cost + "COMPETITION_198","COMPETITION_199","COMPETITION_232","COMPETITION_231", + // Top 4 Heavyweight by combined metric + "COMPETITION_214","COMPETITION_218","COMPETITION_236","COMPETITION_213", + }) CompetitionEnum dataEnum; - @Param() + //@Param() + @Param({"TIM_SORT", "FASTER_FINN_SORT", "IMPL_M_40"}) SortEnum sortEnum; @Override diff --git a/app/src/main/java/de/uni_marburg/powersort/FinnSort/FasterFinnSort.java b/app/src/main/java/de/uni_marburg/powersort/FinnSort/FasterFinnSort.java index 32fb651..c4a4070 100644 --- a/app/src/main/java/de/uni_marburg/powersort/FinnSort/FasterFinnSort.java +++ b/app/src/main/java/de/uni_marburg/powersort/FinnSort/FasterFinnSort.java @@ -172,7 +172,7 @@ public class FasterFinnSort { } // TODO: Verify if this is correct - int stackLen = ((int) Math.ceil(Math.log(rangeSize))) + 2; + int stackLen = ((int) Math.ceil(Math.log(rangeSize) / Math.log(2))) + 2; runBase = new int[stackLen]; runLen = new int[stackLen]; runPower = new int[stackLen]; diff --git a/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_1Test.java b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_1Test.java index b527e7a..064d661 100644 --- a/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_1Test.java +++ b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_1Test.java @@ -1,5 +1,8 @@ package de.uni_marburg.powersort.sort; +import org.junit.jupiter.api.Disabled; + +@Disabled("Disabled in favor of IMPL_M_2") public class IMPL_M_1Test extends AbstractSortTest { IMPL_M_1Test() { sortAlg = SortEnum.IMPL_M_10; diff --git a/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_2Test.java b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_2Test.java new file mode 100644 index 0000000..b6820d0 --- /dev/null +++ b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_2Test.java @@ -0,0 +1,10 @@ +package de.uni_marburg.powersort.sort; + +import org.junit.jupiter.api.Disabled; + +@Disabled("Disabled in favor of IMPL_M_3") +public class IMPL_M_2Test extends AbstractSortTest { + IMPL_M_2Test() { + sortAlg = SortEnum.IMPL_M_20; + } +} \ No newline at end of file diff --git a/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_3Test.java b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_3Test.java new file mode 100644 index 0000000..c7c9f60 --- /dev/null +++ b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_3Test.java @@ -0,0 +1,10 @@ +package de.uni_marburg.powersort.sort; + +import org.junit.jupiter.api.Disabled; + +@Disabled("Disabled in favor of IMPL_M_4") +public class IMPL_M_3Test extends AbstractSortTest { + IMPL_M_3Test() { + sortAlg = SortEnum.IMPL_M_30; + } +} \ No newline at end of file diff --git a/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_4Test.java b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_4Test.java new file mode 100644 index 0000000..b6d9649 --- /dev/null +++ b/app/src/test/java/de/uni_marburg/powersort/sort/IMPL_M_4Test.java @@ -0,0 +1,7 @@ +package de.uni_marburg.powersort.sort; + +public class IMPL_M_4Test extends AbstractSortTest { + IMPL_M_4Test() { + sortAlg = SortEnum.IMPL_M_40; + } +} \ No newline at end of file From 987fd3d30d69f20408285bdffdfdf68844849010 Mon Sep 17 00:00:00 2001 From: M-H9 Date: Tue, 28 Jan 2025 22:58:22 +0100 Subject: [PATCH 3/6] creating IMPL_M_5 and getting better performance than TimSort in every case --- .../powersort/benchmark/JmhCgl.java | 7 +- .../uni_marburg/powersort/MSort/IMPL_M_5.java | 996 ++++++++++++++++++ .../uni_marburg/powersort/sort/SortEnum.java | 3 + 3 files changed, 1003 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_5.java diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java index fd0973f..564c6c5 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java @@ -17,13 +17,14 @@ import org.openjdk.jmh.annotations.State; @State(Scope.Benchmark) public class JmhCgl extends JmhBase { // Either all or a selection of input lists. - @Param() - //@Param({"ASCENDING_RUNS", "ASCENDING_RUNS_WITH_OVERLAP", "MANY_ASCENDING_RUNS", "MANY_ASCENDING_RUNS_WITH_OVERLAP"}) + //@Param({"RANDOM_INTEGERS","ASCENDING_INTEGERS","DESCENDING_INTEGERS"}) + // @Param() + @Param({"ASCENDING_RUNS", "ASCENDING_RUNS_WITH_OVERLAP", "MANY_ASCENDING_RUNS", "MANY_ASCENDING_RUNS_WITH_OVERLAP"}) CglEnum dataEnum; // Either all or a selection of sort implementations. //@Param() - @Param({"TIM_SORT", "FASTER_FINN_SORT", "IMPL_M_40"}) + @Param({"TIM_SORT","IMPL_M_40","IMPL_M_50"}) SortEnum sortEnum; @Override diff --git a/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_5.java b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_5.java new file mode 100644 index 0000000..67d57a9 --- /dev/null +++ b/app/src/main/java/de/uni_marburg/powersort/MSort/IMPL_M_5.java @@ -0,0 +1,996 @@ +package de.uni_marburg.powersort.MSort; + +import java.util.Comparator; + + /* + * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright 2009 Google Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + + /* + * Imported from OpenJDK git repo TimSort.java + */ + + + /** + * A stable, adaptive, iterative mergesort that requires far fewer than + * n lg(n) comparisons when running on partially sorted arrays, while + * offering performance comparable to a traditional mergesort when run + * on random arrays. Like all proper mergesorts, this sort is stable and + * runs O(n log n) time (worst case). In the worst case, this sort requires + * temporary storage space for n/2 object references; in the best case, + * it requires only a small constant amount of space. + * + * This implementation was adapted from Tim Peters's list sort for + * Python, which is described in detail here: + * + * http://svn.python.org/projects/python/trunk/Objects/listsort.txt + * + * Tim's C code may be found here: + * + * http://svn.python.org/projects/python/trunk/Objects/listobject.c + * + * The underlying techniques are described in this paper (and may have + * even earlier origins): + * + * "Optimistic Sorting and Information Theoretic Complexity" + * Peter McIlroy + * SODA (Fourth Annual ACM-SIAM Symposium on Discrete Algorithms), + * pp 467-474, Austin, Texas, 25-27 January 1993. + * + * While the API to this class consists solely of static methods, it is + * (privately) instantiable; a TimSort instance holds the state of an ongoing + * sort, assuming the input array is large enough to warrant the full-blown + * TimSort. Small arrays are sorted in place, using a binary insertion sort. + * + * @author Josh Bloch + */ + public class IMPL_M_5 { + + /** + * This is the minimum sized sequence that will be merged. Shorter + * sequences will be lengthened by calling binarySort. If the entire + * array is less than this length, no merges will be performed. + * + * This constant should be a power of two. It was 64 in Tim Peter's C + * implementation, but 32 was empirically determined to work better in + * this implementation. In the unlikely event that you set this constant + * to be a number that's not a power of two, you'll need to change the + * {@link #minRunLength} computation. + * + * If you decrease this constant, you must change the stackLen + * computation in the TimSort constructor, or you risk an + * ArrayOutOfBounds exception. See listsort.txt for a discussion + * of the minimum stack length required as a function of the length + * of the array being sorted and the minimum merge sequence length. + */ + private static final int MIN_MERGE = 31; + + + /** + * The array being sorted. + */ + private final T[] a; + + /** + * The comparator for this sort. + */ + private final Comparator c; + + /** + * When we get into galloping mode, we stay there until both runs win less + * often than MIN_GALLOP consecutive times. + */ + private static final int MIN_GALLOP = 7; + + /** + * This controls when we get *into* galloping mode. It is initialized + * to MIN_GALLOP. The mergeLo and mergeHi methods nudge it higher for + * random data, and lower for highly structured data. + */ + private int minGallop = MIN_GALLOP; + + /** + * Maximum initial size of tmp array, which is used for merging. The array + * can grow to accommodate demand. + * + * Unlike Tim's original C version, we do not allocate this much storage + * when sorting smaller arrays. This change was required for performance. + */ + private static final int INITIAL_TMP_STORAGE_LENGTH = 255; + + /** + * Temp storage for merges. A workspace array may optionally be + * provided in constructor, and if so will be used as long as it + * is big enough. + */ + private T[] tmp; + private int tmpBase; // base of tmp array slice + private int tmpLen; // length of tmp array slice + + /** + * A stack of pending runs yet to be merged. Run i starts at + * address base[i] and extends for len[i] elements. It's always + * true (so long as the indices are in bounds) that: + * + * runBase[i] + runLen[i] == runBase[i + 1] + * + * so we could cut the storage for this, but it's a minor amount, + * and keeping all the info explicit simplifies the code. + */ + private int stackSize = 0; // Number of pending runs on stack + private final int[] runBase; + private final int[] runLen; + + // Cache for binary search bounds + private final int[] searchBoundsCache; + + /** + * Creates a TimSort instance to maintain the state of an ongoing sort. + * + * @param a the array to be sorted + * @param c the comparator to determine the order of the sort + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array + */ + + + /** + MINE + **/ + + + private final int[] runPower; // Added to track power of each run + private static final int PARALLEL_THRESHOLD = 1 << 16; // 65,536 elements + + + + + + + private IMPL_M_5(T[] a, Comparator c, T[] work, int workBase, int workLen) { + this.a = a; + this.c = c; + + + // Allocate temp storage (which may be increased later if necessary) + // Initialize temp storage with optimized initial size + int len = a.length; + int tlen = (len < 2 * INITIAL_TMP_STORAGE_LENGTH) ? + len >>> 1 : INITIAL_TMP_STORAGE_LENGTH; + + if (work == null || workLen < tlen || workBase + tlen > work.length) { + @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) + T[] newArray = (T[])java.lang.reflect.Array.newInstance( + a.getClass().getComponentType(), tlen); + tmp = newArray; + tmpBase = 0; + tmpLen = tlen; + } else { + tmp = work; + tmpBase = workBase; + tmpLen = workLen; + } + + /* + * Allocate runs-to-be-merged stack (which cannot be expanded). The + * stack length requirements are described in listsort.txt. The C + * version always uses the same stack length (85), but this was + * measured to be too expensive when sorting "mid-sized" arrays (e.g., + * 100 elements) in Java. Therefore, we use smaller (but sufficiently + * large) stack lengths for smaller arrays. The "magic numbers" in the + * computation below must be changed if MIN_MERGE is decreased. See + * the MIN_MERGE declaration above for more information. + * The maximum value of 49 allows for an array up to length + * Integer.MAX_VALUE-4, if array is filled by the worst case stack size + * increasing scenario. More explanations are given in section 4 of: + * http://envisage-project.eu/wp-content/uploads/2015/02/sorting.pdf + */ + // Optimize stack size based on array length + int stackLen = (len < 120 ? 5 : + len < 1542 ? 10 : + len < 119151 ? 19 : 40); + + runBase = new int[stackLen]; + runLen = new int[stackLen]; + runPower = new int[stackLen]; + searchBoundsCache = new int[64]; // Cache for binary search + } + + /* + * The next method (package private and static) constitutes the + * entire API of this class. + */ + + /** + * Sorts the given range, using the given workspace array slice + * for temp storage when possible. This method is designed to be + * invoked from public methods (in class Arrays) after performing + * any necessary array bounds checks and expanding parameters into + * the required forms. + * + * @param a the array to be sorted + * @param lo the index of the first element, inclusive, to be sorted + * @param hi the index of the last element, exclusive, to be sorted + * @param c the comparator to use + * @param work a workspace array (slice) + * @param workBase origin of usable space in work array + * @param workLen usable size of work array + * @since 1.8 + */ + public static void sort(T[] a, int lo, int hi, Comparator c, + T[] work, int workBase, int workLen) { + if (hi - lo < 2) return; + + IMPL_M_5 sorter = new IMPL_M_5<>(a, c, work, workBase, workLen); + sorter.sort(lo, hi); + } + + public void sort(int low, int high) { + if (high - low < MIN_MERGE) { + binaryInsertionSort(a, low, high, c); + return; + } + + int minRun = minRunLength(high - low); + int runStart = low; + while (runStart < high) { + int runLength = countRunAndMakeAscending(a, runStart, high, c); + if (runLength < minRun) { + int force = Math.min(high - runStart, minRun); + binaryInsertionSort(a, runStart, runStart + force, c); + runLength = force; + } + pushRun(runStart, runLength, stackSize); + mergeCollapse(); + runStart += runLength; + } + mergeForceCollapse(); + } + + + private static void binaryInsertionSort(T[] a, int lo, int hi, Comparator c) { + for (int i = lo + 1; i < hi; i++) { + T pivot = a[i]; + int left = lo; + int right = i; + while (left < right) { + int mid = (left + right) >>> 1; + if (c.compare(pivot, a[mid]) < 0) + right = mid; + else + left = mid + 1; + } + System.arraycopy(a, left, a, left + 1, i - left); + a[left] = pivot; + } + } + + /** + * Sorts the specified portion of the specified array using a binary + * insertion sort. This is the best method for sorting small numbers + * of elements. It requires O(n log n) compares, but O(n^2) data + * movement (worst case). + * + * If the initial part of the specified range is already sorted, + * this method can take advantage of it: the method assumes that the + * elements from index {@code lo}, inclusive, to {@code start}, + * exclusive are already sorted. + * + * @param a the array in which a range is to be sorted + * @param lo the index of the first element in the range to be sorted + * @param hi the index after the last element in the range to be sorted + * @param start the index of the first element in the range that is + * not already known to be sorted ({@code lo <= start <= hi}) + * @param c comparator to used for the sort + */ + @SuppressWarnings("fallthrough") + private static void binarySort(T[] a, int lo, int hi, int start, + Comparator c) { + assert lo <= start && start <= hi; + if (start == lo) + start++; + for ( ; start < hi; start++) { + T pivot = a[start]; + + // Set left (and right) to the index where a[start] (pivot) belongs + int left = lo; + int right = start; + assert left <= right; + /* + * Invariants: + * pivot >= all in [lo, left). + * pivot < all in [right, start). + */ + while (left < right) { + int mid = (left + right) >>> 1; + if (c.compare(pivot, a[mid]) < 0) + right = mid; + else + left = mid + 1; + } + assert left == right; + + /* + * The invariants still hold: pivot >= all in [lo, left) and + * pivot < all in [left, start), so pivot belongs at left. Note + * that if there are elements equal to pivot, left points to the + * first slot after them -- that's why this sort is stable. + * Slide elements over to make room for pivot. + */ + int n = start - left; // The number of elements to move + // Switch is just an optimization for arraycopy in default case + switch (n) { + case 2: a[left + 2] = a[left + 1]; + case 1: a[left + 1] = a[left]; + break; + default: System.arraycopy(a, left, a, left + 1, n); + } + a[left] = pivot; + } + } + + /** + * Returns the length of the run beginning at the specified position in + * the specified array and reverses the run if it is descending (ensuring + * that the run will always be ascending when the method returns). + * + * A run is the longest ascending sequence with: + * + * a[lo] <= a[lo + 1] <= a[lo + 2] <= ... + * + * or the longest descending sequence with: + * + * a[lo] > a[lo + 1] > a[lo + 2] > ... + * + * For its intended use in a stable mergesort, the strictness of the + * definition of "descending" is needed so that the call can safely + * reverse a descending sequence without violating stability. + * + * @param a the array in which a run is to be counted and possibly reversed + * @param lo index of the first element in the run + * @param hi index after the last element that may be contained in the run. + * It is required that {@code lo < hi}. + * @param c the comparator to used for the sort + * @return the length of the run beginning at the specified position in + * the specified array + */ + private int countRunAndMakeAscending(T[] a, int lo, int hi, Comparator c) { + int runHi = lo + 1; + if (runHi == hi) return 1; + if (c.compare(a[runHi++], a[lo]) < 0) { + while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) < 0) + runHi++; + reverseRange(a, lo, runHi); + } else { + while (runHi < hi && c.compare(a[runHi], a[runHi - 1]) >= 0) + runHi++; + } + return runHi - lo; + } + + /** + * Reverse the specified range of the specified array. + * + * @param a the array in which a range is to be reversed + * @param lo the index of the first element in the range to be reversed + * @param hi the index after the last element in the range to be reversed + */ + private void reverseRange(T[] a, int lo, int hi) { + hi--; + while (lo < hi) { + T t = a[lo]; + a[lo++] = a[hi]; + a[hi--] = t; + } + } + + /** + * Returns the minimum acceptable run length for an array of the specified + * length. Natural runs shorter than this will be extended with + * {@link #binarySort}. + * + * Roughly speaking, the computation is: + * + * If n < MIN_MERGE, return n (it's too small to bother with fancy stuff). + * Else if n is an exact power of 2, return MIN_MERGE/2. + * Else return an int k, MIN_MERGE/2 <= k <= MIN_MERGE, such that n/k + * is close to, but strictly less than, an exact power of 2. + * + * For the rationale, see listsort.txt. + * + * @param n the length of the array to be sorted + * @return the length of the minimum run to be merged + */ + private int minRunLength(int n) { + int r = 0; + while (n >= MIN_MERGE) { + r |= (n & 1); + n >>= 1; + } + return n + r; + } + + /** + * Pushes the specified run onto the pending-run stack. + * + * @param runBase index of the first element in the run + * @param runLen the number of elements in the run + */ + private void pushRun(int runBase, int runLen, int stackPos) { + this.runBase[stackPos] = runBase; + this.runLen[stackPos] = runLen; + stackSize++; + } + + + private int computePower(int start1, int end1, int start2, int end2, int totalLength) { + if (totalLength == 0) return 0; + + // Calculate normalized positions (0 to 1 range) + double mid1 = (start1 + (end1 - start1) / 2.0) / totalLength; + double mid2 = (start2 + (end2 - start2) / 2.0) / totalLength; + + // Fast path for equal midpoints + if (Math.abs(mid1 - mid2) < 1e-10) { + return 64; // Maximum power for identical positions + } + + // Count matching bits in binary representation + int power = 0; + double a = mid1; + double b = mid2; + + while (Math.floor(a) == Math.floor(b) && power < 64) { + a = (a - Math.floor(a)) * 2; + b = (b - Math.floor(b)) * 2; + power++; + } + + return power; + } + + + /** + * Examines the stack of runs waiting to be merged and merges adjacent runs + * until the stack invariants are reestablished: + * + * 1. runLen[i - 3] > runLen[i - 2] + runLen[i - 1] + * 2. runLen[i - 2] > runLen[i - 1] + * + * This method is called each time a new run is pushed onto the stack, + * so the invariants are guaranteed to hold for i < stackSize upon + * entry to the method. + * + * Thanks to Stijn de Gouw, Jurriaan Rot, Frank S. de Boer, + * Richard Bubel and Reiner Hahnle, this is fixed with respect to + * the analysis in "On the Worst-Case Complexity of TimSort" by + * Nicolas Auger, Vincent Jug, Cyril Nicaud, and Carine Pivoteau. + */ + private void mergeCollapse() { + while (stackSize > 1) { + int n = stackSize - 2; + if (n > 0 && runLen[n - 1] <= runLen[n] + runLen[n + 1]) { + if (runLen[n - 1] < runLen[n + 1]) { + mergeAt(n - 1); + } else { + mergeAt(n); + } + } else if (runLen[n] <= runLen[n + 1]) { + mergeAt(n); + } else { + break; + } + } + } + + + /** + * Merges all runs on the stack until only one remains. This method is + * called once, to complete the sort. + */ + private void mergeForceCollapse() { + while (stackSize > 1) { + int n = stackSize - 2; + if (n > 0 && runLen[n - 1] < runLen[n + 1]) { + n--; + } + mergeAt(n); + } + } + + + + /** + * Merges the two runs at stack indices i and i+1. Run i must be + * the penultimate or antepenultimate run on the stack. In other words, + * i must be equal to stackSize-2 or stackSize-3. + * + * @param i stack index of the first of the two runs to merge + */ + private void mergeAt(int i) { + assert stackSize >= 2; + assert i >= 0; + assert i == stackSize - 2 || i == stackSize - 3; + + int base1 = runBase[i]; + int len1 = runLen[i]; + int base2 = runBase[i + 1]; + int len2 = runLen[i + 1]; + assert len1 > 0 && len2 > 0; + assert base1 + len1 == base2; + +// if (i < 0 || i >= stackSize - 1) { +// throw new IllegalStateException("mergeAt called with invalid index: " + i); +// } +// System.out.println("Merging at index: " + i + " with stackSize: " + stackSize); + + /* + * Record the length of the combined runs; if i is the 3rd-last + * run now, also slide over the last run (which isn't involved + * in this merge). The current run (i+1) goes away in any case. + */ + runLen[i] = len1 + len2; + + // Update runPower before modifying the stack structure + runPower[i] = runPower[i + 1]; // Transfer power from the absorbed run + + if (i == stackSize - 3) { + runBase[i + 1] = runBase[i + 2]; + runLen[i + 1] = runLen[i + 2]; + runPower[i + 1] = runPower[i + 2]; // Also slide the power value + } + stackSize--; + + /* + * Find where the first element of run2 goes in run1. Prior elements + * in run1 can be ignored (because they're already in place). + */ + int k = gallopRight(a[base2], a, base1, len1, 0, c); + assert k >= 0; + base1 += k; + len1 -= k; + if (len1 == 0) + return; + + /* + * Find where the last element of run1 goes in run2. Subsequent elements + * in run2 can be ignored (because they're already in place). + */ + len2 = gallopLeft(a[base1 + len1 - 1], a, base2, len2, len2 - 1, c); + assert len2 >= 0; + if (len2 == 0) + return; + + // Merge remaining runs, using tmp array with min(len1, len2) elements + if (len1 <= len2) + mergeLo(base1, len1, base2, len2); + else + mergeHi(base1, len1, base2, len2); + } + + /** + * Locates the position at which to insert the specified key into the + * specified sorted range; if the range contains an element equal to key, + * returns the index of the leftmost equal element. + * + * @param key the key whose insertion point to search for + * @param a the array in which to search + * @param base the index of the first element in the range + * @param len the length of the range; must be > 0 + * @param hint the index at which to begin the search, 0 <= hint < n. + * The closer hint is to the result, the faster this method will run. + * @param c the comparator used to order the range, and to search + * @return the int k, 0 <= k <= n such that a[b + k - 1] < key <= a[b + k], + * pretending that a[b - 1] is minus infinity and a[b + n] is infinity. + * In other words, key belongs at index b + k; or in other words, + * the first k elements of a should precede key, and the last n - k + * should follow it. + */ + private static int gallopLeft(T key, T[] a, int base, int len, int hint, + Comparator c) { + int lastOfs = 0; + int ofs = 1; + + if (c.compare(key, a[base + hint]) > 0) { + int maxOfs = len - hint; + while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) > 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) ofs = maxOfs; + } + if (ofs > maxOfs) ofs = maxOfs; + + lastOfs += hint; + ofs += hint; + } else { + int maxOfs = hint + 1; + while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) <= 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) ofs = maxOfs; + } + if (ofs > maxOfs) ofs = maxOfs; + + int tmp = lastOfs; + lastOfs = hint - ofs; + ofs = hint - tmp; + } + + lastOfs++; + while (lastOfs < ofs) { + int m = lastOfs + ((ofs - lastOfs) >>> 1); + if (c.compare(key, a[base + m]) > 0) { + lastOfs = m + 1; + } else { + ofs = m; + } + } + return ofs; + } + + /** + * Like gallopLeft, except that if the range contains an element equal to + * key, gallopRight returns the index after the rightmost equal element. + * + * @param key the key whose insertion point to search for + * @param a the array in which to search + * @param base the index of the first element in the range + * @param len the length of the range; must be > 0 + * @param hint the index at which to begin the search, 0 <= hint < n. + * The closer hint is to the result, the faster this method will run. + * @param c the comparator used to order the range, and to search + * @return the int k, 0 <= k <= n such that a[b + k - 1] <= key < a[b + k] + */ + private static int gallopRight(T key, T[] a, int base, int len, + int hint, Comparator c) { + assert len > 0 && hint >= 0 && hint < len; + + int ofs = 1; + int lastOfs = 0; + if (c.compare(key, a[base + hint]) < 0) { + // Gallop left until a[b+hint - ofs] <= key < a[b+hint - lastOfs] + int maxOfs = hint + 1; + while (ofs < maxOfs && c.compare(key, a[base + hint - ofs]) < 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) // int overflow + ofs = maxOfs; + } + if (ofs > maxOfs) + ofs = maxOfs; + + // Make offsets relative to b + int tmp = lastOfs; + lastOfs = hint - ofs; + ofs = hint - tmp; + } else { // a[b + hint] <= key + // Gallop right until a[b+hint + lastOfs] <= key < a[b+hint + ofs] + int maxOfs = len - hint; + while (ofs < maxOfs && c.compare(key, a[base + hint + ofs]) >= 0) { + lastOfs = ofs; + ofs = (ofs << 1) + 1; + if (ofs <= 0) // int overflow + ofs = maxOfs; + } + if (ofs > maxOfs) + ofs = maxOfs; + + // Make offsets relative to b + lastOfs += hint; + ofs += hint; + } + assert -1 <= lastOfs && lastOfs < ofs && ofs <= len; + + /* + * Now a[b + lastOfs] <= key < a[b + ofs], so key belongs somewhere to + * the right of lastOfs but no farther right than ofs. Do a binary + * search, with invariant a[b + lastOfs - 1] <= key < a[b + ofs]. + */ + lastOfs++; + while (lastOfs < ofs) { + int m = lastOfs + ((ofs - lastOfs) >>> 1); + + if (c.compare(key, a[base + m]) < 0) + ofs = m; // key < a[b + m] + else + lastOfs = m + 1; // a[b + m] <= key + } + assert lastOfs == ofs; // so a[b + ofs - 1] <= key < a[b + ofs] + return ofs; + } + + /** + * Merges two adjacent runs in place, in a stable fashion. The first + * element of the first run must be greater than the first element of the + * second run (a[base1] > a[base2]), and the last element of the first run + * (a[base1 + len1-1]) must be greater than all elements of the second run. + * + * For performance, this method should be called only when len1 <= len2; + * its twin, mergeHi should be called if len1 >= len2. (Either method + * may be called if len1 == len2.) + * + * @param base1 index of first element in first run to be merged + * @param len1 length of first run to be merged (must be > 0) + * @param base2 index of first element in second run to be merged + * (must be aBase + aLen) + * @param len2 length of second run to be merged (must be > 0) + */ + private void mergeLo(int base1, int len1, int base2, int len2) { + assert len1 > 0 && len2 > 0 && base1 + len1 == base2; + + // Copy first run into temp array + T[] a = this.a; // For performance + T[] tmp = ensureCapacity(len1); + int cursor1 = tmpBase; // Indexes into tmp array + int cursor2 = base2; // Indexes int a + int dest = base1; // Indexes int a + System.arraycopy(a, base1, tmp, cursor1, len1); + + // Move first element of second run and deal with degenerate cases + a[dest++] = a[cursor2++]; + if (--len2 == 0) { + System.arraycopy(tmp, cursor1, a, dest, len1); + return; + } + if (len1 == 1) { + System.arraycopy(a, cursor2, a, dest, len2); + a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge + return; + } + + Comparator c = this.c; // Use local variable for performance + int minGallop = this.minGallop; // " " " " " + outer: + while (true) { + int count1 = 0; // Number of times in a row that first run won + int count2 = 0; // Number of times in a row that second run won + + /* + * Do the straightforward thing until (if ever) one run starts + * winning consistently. + */ + do { + assert len1 > 1 && len2 > 0; + if (c.compare(a[cursor2], tmp[cursor1]) < 0) { + a[dest++] = a[cursor2++]; + count2++; + count1 = 0; + if (--len2 == 0) + break outer; + } else { + a[dest++] = tmp[cursor1++]; + count1++; + count2 = 0; + if (--len1 == 1) + break outer; + } + } while ((count1 | count2) < minGallop); + + /* + * One run is winning so consistently that galloping may be a + * huge win. So try that, and continue galloping until (if ever) + * neither run appears to be winning consistently anymore. + */ + do { + assert len1 > 1 && len2 > 0; + count1 = gallopRight(a[cursor2], tmp, cursor1, len1, 0, c); + if (count1 != 0) { + System.arraycopy(tmp, cursor1, a, dest, count1); + dest += count1; + cursor1 += count1; + len1 -= count1; + if (len1 <= 1) // len1 == 1 || len1 == 0 + break outer; + } + a[dest++] = a[cursor2++]; + if (--len2 == 0) + break outer; + + count2 = gallopLeft(tmp[cursor1], a, cursor2, len2, 0, c); + if (count2 != 0) { + System.arraycopy(a, cursor2, a, dest, count2); + dest += count2; + cursor2 += count2; + len2 -= count2; + if (len2 == 0) + break outer; + } + a[dest++] = tmp[cursor1++]; + if (--len1 == 1) + break outer; + minGallop--; + } while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP); + if (minGallop < 0) + minGallop = 0; + minGallop += 2; // Penalize for leaving gallop mode + } // End of "outer" loop + this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field + + if (len1 == 1) { + assert len2 > 0; + System.arraycopy(a, cursor2, a, dest, len2); + a[dest + len2] = tmp[cursor1]; // Last elt of run 1 to end of merge + } else if (len1 == 0) { + throw new IllegalArgumentException( + "Comparison method violates its general contract!"); + } else { + assert len2 == 0; + assert len1 > 1; + System.arraycopy(tmp, cursor1, a, dest, len1); + } + } + + /** + * Like mergeLo, except that this method should be called only if + * len1 >= len2; mergeLo should be called if len1 <= len2. (Either method + * may be called if len1 == len2.) + * + * @param base1 index of first element in first run to be merged + * @param len1 length of first run to be merged (must be > 0) + * @param base2 index of first element in second run to be merged + * (must be aBase + aLen) + * @param len2 length of second run to be merged (must be > 0) + */ + private void mergeHi(int base1, int len1, int base2, int len2) { + assert len1 > 0 && len2 > 0 && base1 + len1 == base2; + + // Copy second run into temp array + T[] a = this.a; // For performance + T[] tmp = ensureCapacity(len2); + int tmpBase = this.tmpBase; + System.arraycopy(a, base2, tmp, tmpBase, len2); + + int cursor1 = base1 + len1 - 1; // Indexes into a + int cursor2 = tmpBase + len2 - 1; // Indexes into tmp array + int dest = base2 + len2 - 1; // Indexes into a + + // Move last element of first run and deal with degenerate cases + a[dest--] = a[cursor1--]; + if (--len1 == 0) { + System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); + return; + } + if (len2 == 1) { + dest -= len1; + cursor1 -= len1; + System.arraycopy(a, cursor1 + 1, a, dest + 1, len1); + a[dest] = tmp[cursor2]; + return; + } + + Comparator c = this.c; // Use local variable for performance + int minGallop = this.minGallop; // " " " " " + outer: + while (true) { + int count1 = 0; // Number of times in a row that first run won + int count2 = 0; // Number of times in a row that second run won + + /* + * Do the straightforward thing until (if ever) one run + * appears to win consistently. + */ + do { + assert len1 > 0 && len2 > 1; + if (c.compare(tmp[cursor2], a[cursor1]) < 0) { + a[dest--] = a[cursor1--]; + count1++; + count2 = 0; + if (--len1 == 0) + break outer; + } else { + a[dest--] = tmp[cursor2--]; + count2++; + count1 = 0; + if (--len2 == 1) + break outer; + } + } while ((count1 | count2) < minGallop); + + /* + * One run is winning so consistently that galloping may be a + * huge win. So try that, and continue galloping until (if ever) + * neither run appears to be winning consistently anymore. + */ + do { + assert len1 > 0 && len2 > 1; + count1 = len1 - gallopRight(tmp[cursor2], a, base1, len1, len1 - 1, c); + if (count1 != 0) { + dest -= count1; + cursor1 -= count1; + len1 -= count1; + System.arraycopy(a, cursor1 + 1, a, dest + 1, count1); + if (len1 == 0) + break outer; + } + a[dest--] = tmp[cursor2--]; + if (--len2 == 1) + break outer; + + count2 = len2 - gallopLeft(a[cursor1], tmp, tmpBase, len2, len2 - 1, c); + if (count2 != 0) { + dest -= count2; + cursor2 -= count2; + len2 -= count2; + System.arraycopy(tmp, cursor2 + 1, a, dest + 1, count2); + if (len2 <= 1) // len2 == 1 || len2 == 0 + break outer; + } + a[dest--] = a[cursor1--]; + if (--len1 == 0) + break outer; + minGallop--; + } while (count1 >= MIN_GALLOP | count2 >= MIN_GALLOP); + if (minGallop < 0) + minGallop = 0; + minGallop += 2; // Penalize for leaving gallop mode + } // End of "outer" loop + this.minGallop = minGallop < 1 ? 1 : minGallop; // Write back to field + + if (len2 == 1) { + assert len1 > 0; + dest -= len1; + cursor1 -= len1; + System.arraycopy(a, cursor1 + 1, a, dest + 1, len1); + a[dest] = tmp[cursor2]; // Move first elt of run2 to front of merge + } else if (len2 == 0) { + throw new IllegalArgumentException( + "Comparison method violates its general contract!"); + } else { + assert len1 == 0; + assert len2 > 0; + System.arraycopy(tmp, tmpBase, a, dest - (len2 - 1), len2); + } + } + + /** + * Ensures that the external array tmp has at least the specified + * number of elements, increasing its size if necessary. The size + * increases exponentially to ensure amortized linear time complexity. + * + * @param minCapacity the minimum required capacity of the tmp array + * @return tmp, whether or not it grew + */ + private T[] ensureCapacity(int minCapacity) { + if (tmpLen < minCapacity) { + // Compute smallest power of 2 > minCapacity + int newSize = -1 >>> Integer.numberOfLeadingZeros(minCapacity); + newSize++; + + if (newSize < 0) // Not bloody likely! + newSize = minCapacity; + else + newSize = Math.min(newSize, a.length >>> 1); + + @SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"}) + T[] newArray = (T[])java.lang.reflect.Array.newInstance + (a.getClass().getComponentType(), newSize); + tmp = newArray; + tmpLen = newSize; + tmpBase = 0; + } + return tmp; + } + } \ No newline at end of file diff --git a/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java b/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java index 0d8b1b7..d8a1c3d 100644 --- a/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java +++ b/app/src/main/java/de/uni_marburg/powersort/sort/SortEnum.java @@ -6,6 +6,7 @@ import de.uni_marburg.powersort.MSort.IMPL_M_1; import de.uni_marburg.powersort.MSort.IMPL_M_2; import de.uni_marburg.powersort.MSort.IMPL_M_3; import de.uni_marburg.powersort.MSort.IMPL_M_4; +import de.uni_marburg.powersort.MSort.IMPL_M_5; import de.uni_marburg.powersort.benchmark.NaturalOrder; import de.uni_marburg.powersort.sort.dpqs.DualPivotQuicksort; @@ -18,6 +19,7 @@ public enum SortEnum { IMPL_M_20, IMPL_M_30, IMPL_M_40, + IMPL_M_50, DPQS, QUICK_SORT, MERGE_SORT, @@ -36,6 +38,7 @@ public enum SortEnum { case IMPL_M_20 -> array -> IMPL_M_2.powerSort(array,NaturalOrder.INSTANCE); case IMPL_M_30 -> array -> IMPL_M_3.powerSort(array,NaturalOrder.INSTANCE); case IMPL_M_40 -> array -> IMPL_M_4.sort(array, 0, array.length, NaturalOrder.INSTANCE, null, 0, 0); + case IMPL_M_50 -> array -> IMPL_M_5.sort(array, 0, array.length, NaturalOrder.INSTANCE, null, 0, 0); case FASTER_FINN_SORT -> array -> FasterFinnSort.sort(array, 0, array.length, NaturalOrder.INSTANCE, null, 0, 0); case ASORT -> array -> ASort.sort(array, NaturalOrder.INSTANCE); }; From 3f3eef67d44a6302345a91da680bf0b5b8af6963 Mon Sep 17 00:00:00 2001 From: Daniel Langbein Date: Wed, 29 Jan 2025 16:01:03 +0100 Subject: [PATCH 4/6] benchmark: changed param --- .../de/uni_marburg/powersort/benchmark/JmhCgl.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java index fd0973f..eed492c 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java @@ -17,13 +17,17 @@ import org.openjdk.jmh.annotations.State; @State(Scope.Benchmark) public class JmhCgl extends JmhBase { // Either all or a selection of input lists. - @Param() - //@Param({"ASCENDING_RUNS", "ASCENDING_RUNS_WITH_OVERLAP", "MANY_ASCENDING_RUNS", "MANY_ASCENDING_RUNS_WITH_OVERLAP"}) + //@Param() + @Param({ + //"RANDOM_INTEGERS", + "ASCENDING_RUNS", "ASCENDING_RUNS_WITH_OVERLAP", + "MANY_ASCENDING_RUNS", "MANY_ASCENDING_RUNS_WITH_OVERLAP" + }) CglEnum dataEnum; // Either all or a selection of sort implementations. //@Param() - @Param({"TIM_SORT", "FASTER_FINN_SORT", "IMPL_M_40"}) + @Param({"TIM_SORT", "FASTER_FINN_SORT", "IMPL_M_50"}) SortEnum sortEnum; @Override @@ -42,4 +46,4 @@ public class JmhCgl extends JmhBase { public void benchmark() { sortImpl.sort(workingCopy); } -} \ No newline at end of file +} From 1b944c38f04bc968f910346b36dbe7510b46b64d Mon Sep 17 00:00:00 2001 From: Daniel Langbein Date: Wed, 29 Jan 2025 16:49:37 +0100 Subject: [PATCH 5/6] benchmark: adjusted time --- README.md | 2 +- .../java/de/uni_marburg/powersort/benchmark/JmhBase.java | 2 +- .../java/de/uni_marburg/powersort/benchmark/JmhCgl.java | 7 ++++++- .../main/java/de/uni_marburg/powersort/data/CglEnum.java | 6 +++--- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 3523f7f..172a6e2 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ Run Custom Benchmark (CGM) with #### Run JMH with CGL and Powersort competition lists ```shell -./gradlew jmh +./gradlew jmh --rerun ``` - To benchmark only one of the different list collections, see `jmh { excludes }` at the bottom of [./app/build.gradle.kts](./app/build.gradle.kts). diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java index fa72c5a..7b5202a 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhBase.java @@ -45,7 +45,7 @@ import java.util.concurrent.TimeUnit; // - Until the 17th spike of up to +750% (Maybe JVM optimizations happening?) // - After 40th constant slowdown of around +10% (Maybe CPU frequency adjustments?) // Thus, we need at least ~50 warmup iterations! -@Warmup(iterations = 50) +@Warmup(iterations = 60) @Measurement(iterations = 6) /* diff --git a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java index eed492c..2990d1a 100644 --- a/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java +++ b/app/src/jmh/java/de/uni_marburg/powersort/benchmark/JmhCgl.java @@ -27,7 +27,12 @@ public class JmhCgl extends JmhBase { // Either all or a selection of sort implementations. //@Param() - @Param({"TIM_SORT", "FASTER_FINN_SORT", "IMPL_M_50"}) + @Param({ + "TIM_SORT", + "FASTER_FINN_SORT", + //"IMPL_M_40", + "IMPL_M_50", + }) SortEnum sortEnum; @Override diff --git a/app/src/main/java/de/uni_marburg/powersort/data/CglEnum.java b/app/src/main/java/de/uni_marburg/powersort/data/CglEnum.java index a7a37bd..ac6b95f 100644 --- a/app/src/main/java/de/uni_marburg/powersort/data/CglEnum.java +++ b/app/src/main/java/de/uni_marburg/powersort/data/CglEnum.java @@ -22,14 +22,14 @@ public enum CglEnum implements DataEnum { int runs = 3_010; int runLength = 3_010; - int manyRuns = 30_000; + int manyRuns = 120_000; int shortRunLength = 50; // Constant factors double a = 0.96; double b = 0.25; double c = 0.81; - double d = 1.0; + double d = 0.85; return switch (this) { case RANDOM_INTEGERS -> new RandomIntegers(listSize, seed); @@ -40,7 +40,7 @@ public enum CglEnum implements DataEnum { AscendingRuns.newAscendingRuns((int) (c * runs), (int) (c * runLength), (int) (-0.5 * c * runLength)); case MANY_ASCENDING_RUNS -> AscendingRuns.newAscendingRuns(manyRuns, shortRunLength, -1 * shortRunLength); case MANY_ASCENDING_RUNS_WITH_OVERLAP -> - AscendingRuns.newAscendingRuns((int) (d * manyRuns), (int) (d * shortRunLength), (int) (-0.5 * d * shortRunLength)); + AscendingRuns.newAscendingRuns((int) (d * manyRuns), shortRunLength, (int) (-0.5 * shortRunLength)); }; } } From acbefbe5540457c88ed5e9b6e24882e595e9a3ad Mon Sep 17 00:00:00 2001 From: Daniel Langbein Date: Wed, 29 Jan 2025 17:03:04 +0100 Subject: [PATCH 6/6] CI: run JMH benchmark --- .gitlab-ci.yml | 13 +++++++++++++ app/build.gradle.kts | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2c310ef..c79776f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -10,3 +10,16 @@ java: when: always reports: junit: app/build/test-results/test/**/TEST-*.xml + expire_in: 6 month + +jmh: + image: alpine:latest + stage: test + script: + - apk --no-cache add openjdk23 gradle --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing/ + - gradle jmh --no-daemon + # https://docs.gitlab.com/ee/ci/jobs/job_artifacts.html + artifacts: + paths: + - app/build/reports/jmh/* + expire_in: 6 month diff --git a/app/build.gradle.kts b/app/build.gradle.kts index af1ddc6..522971a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -97,9 +97,9 @@ jmh { forceGC = true // If human output is saved, it won't be written to stdout while running the benchmark! - //humanOutputFile = project.file("${project.layout.buildDirectory.get()}/reports/jmh/human.txt") + humanOutputFile = project.file("${project.layout.buildDirectory.get()}/reports/jmh/human.txt") - resultsFile = project.file("${project.layout.buildDirectory.get()}/reports/jmh/results.txt") + resultsFile = project.file("${project.layout.buildDirectory.get()}/reports/jmh/results.csv") resultFormat = "CSV" excludes = listOf(