From 55a7c1db00d59de65347e1992616310f0aa092b7 Mon Sep 17 00:00:00 2001 From: Viktor Lofgren Date: Mon, 13 May 2024 18:38:43 +0200 Subject: [PATCH] (array, experimental) Call C++ helper methods to do some low level stuff a bit faster than is possible with Java --- code/libraries/array/build.gradle | 3 + code/libraries/array/cpp/build.gradle | 26 ++++ code/libraries/array/cpp/compile.sh | 8 ++ .../cpp/java/nu/marginalia/NativeAlgos.java | 133 +++++++++++++++++ code/libraries/array/cpp/resources/.gitignore | 1 + .../array/cpp/src/main/cpp/cpphelpers.cpp | 136 ++++++++++++++++++ .../array/cpp/src/main/public/cpphelpers.hpp | 14 ++ .../java/nu/marginalia/array/LongArray.java | 1 + .../marginalia/array/algo/LongArrayBase.java | 7 + .../array/algo/LongArraySearch.java | 70 ++++----- .../marginalia/array/algo/LongArraySort.java | 42 ++---- .../ReferenceImplIntArrayDelegate.java | 53 ------- .../ReferenceImplLongArrayDelegate.java | 57 -------- .../array/delegate/ShiftedLongArray.java | 45 ++++-- .../array/page/SegmentLongArray.java | 32 +++++ .../array/page/UnsafeLongArray.java | 31 ++++ .../marginalia/array/SimulatedNaiveArray.java | 53 ------- .../marginalia/array/page/FoldBenchmark.java | 64 --------- .../array/page/QuicksortBenchmark.java | 61 ++------ .../array/algo/LongArraySearchTest.java | 21 ++- .../array/algo/LongArraySortTest.java | 28 ++++ settings.gradle | 1 + 22 files changed, 518 insertions(+), 369 deletions(-) create mode 100644 code/libraries/array/cpp/build.gradle create mode 100644 code/libraries/array/cpp/compile.sh create mode 100644 code/libraries/array/cpp/java/nu/marginalia/NativeAlgos.java create mode 100644 code/libraries/array/cpp/resources/.gitignore create mode 100644 code/libraries/array/cpp/src/main/cpp/cpphelpers.cpp create mode 100644 code/libraries/array/cpp/src/main/public/cpphelpers.hpp delete mode 100644 code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplIntArrayDelegate.java delete mode 100644 code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplLongArrayDelegate.java delete mode 100644 code/libraries/array/src/jmh/java/nu/marginalia/array/SimulatedNaiveArray.java delete mode 100644 code/libraries/array/src/jmh/java/nu/marginalia/array/page/FoldBenchmark.java diff --git a/code/libraries/array/build.gradle b/code/libraries/array/build.gradle index 2e1b8051..4c88a870 100644 --- a/code/libraries/array/build.gradle +++ b/code/libraries/array/build.gradle @@ -9,6 +9,7 @@ java { } } + apply from: "$rootProject.projectDir/srcsets.gradle" dependencies { @@ -20,6 +21,8 @@ dependencies { implementation libs.lz4 implementation libs.guava + implementation project(':code:libraries:array:cpp') + testImplementation libs.bundles.slf4j.test testImplementation libs.bundles.junit testImplementation libs.mockito diff --git a/code/libraries/array/cpp/build.gradle b/code/libraries/array/cpp/build.gradle new file mode 100644 index 00000000..8d4311b2 --- /dev/null +++ b/code/libraries/array/cpp/build.gradle @@ -0,0 +1,26 @@ +plugins { + id 'java' +} + +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(rootProject.ext.jvmVersion)) + } +} + +dependencies { + implementation libs.bundles.slf4j +} + +apply from: "$rootProject.projectDir/srcsets.gradle" + +// We use a custom task to compile the C++ code into a shared library +// with a shellscript as gradle's c++ tasks are kind of insufferable + +tasks.register('compileCpp', Exec) { + inputs.files('src/main/cpp/cpphelpers.cpp', 'src/main/public/cpphelpers.h') + outputs.file 'resources/libcpp.so' + commandLine 'sh', 'compile.sh' +} + +processResources.dependsOn('compileCpp') \ No newline at end of file diff --git a/code/libraries/array/cpp/compile.sh b/code/libraries/array/cpp/compile.sh new file mode 100644 index 00000000..32792add --- /dev/null +++ b/code/libraries/array/cpp/compile.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +if ! which g++ > /dev/null; then + echo "g++ not found, skipping compilation" + exit 0 +fi + +c++ -O3 -march=native -shared -fPIC -Isrc/main/public src/main/cpp/*.cpp -o resources/libcpp.so \ No newline at end of file diff --git a/code/libraries/array/cpp/java/nu/marginalia/NativeAlgos.java b/code/libraries/array/cpp/java/nu/marginalia/NativeAlgos.java new file mode 100644 index 00000000..faf081d9 --- /dev/null +++ b/code/libraries/array/cpp/java/nu/marginalia/NativeAlgos.java @@ -0,0 +1,133 @@ +package nu.marginalia; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileOutputStream; +import java.lang.foreign.*; +import java.lang.invoke.MethodHandle; +import java.nio.file.Path; + +import static java.lang.foreign.ValueLayout.ADDRESS; +import static java.lang.foreign.ValueLayout.JAVA_LONG; + +public class NativeAlgos { + private final MethodHandle qsortHandle; + private final MethodHandle qsort128Handle; + private final MethodHandle linearSearch64Handle; + private final MethodHandle linearSearch128Handle; + private final MethodHandle binarySearch128Handle; + private final MethodHandle binarySearch64UpperHandle; + + public static final NativeAlgos instance; + public static final boolean isAvailable; + + private static final Logger logger = LoggerFactory.getLogger(NativeAlgos.class); + + private NativeAlgos(Path libFile) throws Exception { + var libraryLookup = SymbolLookup.libraryLookup(libFile, Arena.global()); + var nativeLinker = Linker.nativeLinker(); + + var handle = libraryLookup.find("ms_sort_64").get(); + qsortHandle = nativeLinker.downcallHandle(handle, FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG)); + + handle = libraryLookup.find("ms_sort_128").get(); + qsort128Handle = nativeLinker.downcallHandle(handle, + FunctionDescriptor.ofVoid(ADDRESS, JAVA_LONG, JAVA_LONG)); + + handle = libraryLookup.find("ms_linear_search_64").get(); + linearSearch64Handle = nativeLinker.downcallHandle(handle, + FunctionDescriptor.of(JAVA_LONG, JAVA_LONG, ADDRESS, JAVA_LONG, JAVA_LONG)); + + handle = libraryLookup.find("ms_linear_search_128").get(); + linearSearch128Handle = nativeLinker.downcallHandle(handle, + FunctionDescriptor.of(JAVA_LONG, JAVA_LONG, ADDRESS, JAVA_LONG, JAVA_LONG)); + + handle = libraryLookup.find("ms_binary_search_128").get(); + binarySearch128Handle = nativeLinker.downcallHandle(handle, + FunctionDescriptor.of(JAVA_LONG, JAVA_LONG, ADDRESS, JAVA_LONG, JAVA_LONG)); + + handle = libraryLookup.find("ms_binary_search_64upper").get(); + binarySearch64UpperHandle = nativeLinker.downcallHandle(handle, + FunctionDescriptor.of(JAVA_LONG, JAVA_LONG, ADDRESS, JAVA_LONG, JAVA_LONG)); + } + + static { + Path libFile; + NativeAlgos nativeAlgosI = null; + // copy resource to temp file + try (var is = NativeAlgos.class.getClassLoader().getResourceAsStream("libcpp.so")) { + var tempFile = File.createTempFile("libcpp", ".so"); + tempFile.deleteOnExit(); + + try (var os = new FileOutputStream(tempFile)) { + is.transferTo(os); + } + + libFile = tempFile.toPath(); + nativeAlgosI = new NativeAlgos(libFile); + } + catch (Exception e) { + + } + + instance = nativeAlgosI; + isAvailable = instance != null; + } + + + public static void sort(MemorySegment ms, long start, long end) { + try { + instance.qsortHandle.invoke(ms, start, end); + } + catch (Throwable t) { + throw new RuntimeException("Failed to invoke native function", t); + } + } + + public static void sort128(MemorySegment ms, long start, long end) { + try { + instance.qsort128Handle.invoke(ms, start, end); + } + catch (Throwable t) { + throw new RuntimeException("Failed to invoke native function", t); + } + } + + public static long linearSearch64(long key, MemorySegment ms, long start, long end) { + try { + return (long) instance.linearSearch64Handle.invoke(key, ms, start, end); + } + catch (Throwable t) { + throw new RuntimeException("Failed to invoke native function", t); + } + } + + public static long linearSearch128(long key, MemorySegment ms, long start, long end) { + try { + return (long) instance.linearSearch128Handle.invoke(key, ms, start, end); + } + catch (Throwable t) { + throw new RuntimeException("Failed to invoke native function", t); + } + } + + public static long binarySearch128(long key, MemorySegment ms, long start, long end) { + try { + return (long) instance.binarySearch128Handle.invoke(key, ms, start, end); + } + catch (Throwable t) { + throw new RuntimeException("Failed to invoke native function", t); + } + } + + public static long binarySearch64Upper(long key, MemorySegment ms, long start, long end) { + try { + return (long) instance.binarySearch64UpperHandle.invoke(key, ms, start, end); + } + catch (Throwable t) { + throw new RuntimeException("Failed to invoke native function", t); + } + } +} diff --git a/code/libraries/array/cpp/resources/.gitignore b/code/libraries/array/cpp/resources/.gitignore new file mode 100644 index 00000000..01b07345 --- /dev/null +++ b/code/libraries/array/cpp/resources/.gitignore @@ -0,0 +1 @@ +../../resources/cpp/libcpp.so \ No newline at end of file diff --git a/code/libraries/array/cpp/src/main/cpp/cpphelpers.cpp b/code/libraries/array/cpp/src/main/cpp/cpphelpers.cpp new file mode 100644 index 00000000..f359b512 --- /dev/null +++ b/code/libraries/array/cpp/src/main/cpp/cpphelpers.cpp @@ -0,0 +1,136 @@ +#include "cpphelpers.hpp" +#include +#include + +struct p2 { + int64_t a; + int64_t b; +}; + +void ms_sort_64(int64_t* area, uint64_t start, uint64_t end) { + std::sort(&area[start], &area[end]); +} + +void ms_sort_128(int64_t* area, uint64_t start, uint64_t end) { + struct p2 *startp = (struct p2 *) &area[start]; + struct p2 *endp = (struct p2 *) &area[end]; + + // sort based on the first element of the pair + std::sort(startp, endp, [](struct p2& fst, struct p2& snd) { + return fst.a < snd.a; + }); +} + +int64_t encodeSearchMiss64(int64_t value) { + return -1 - std::max(int64_t(0), value); +} +int64_t encodeSearchMiss128(int64_t value) { + return -2 - std::max(int64_t(0), value); +} + +int64_t decodeSearchMiss64(long value) { + return -value - 1; +} +int64_t decodeSearchMiss128(long value) { + return -value - 2; +} + +int64_t ms_linear_search_64(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex) { + uint64_t pos = fromIndex; + for (; pos < toIndex; pos++) { + int64_t val = area[pos]; + + if (val == key) return pos; + if (val > key) break; + } + + return encodeSearchMiss64(pos - 1); +} + +int64_t ms_linear_search_128(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex) { + uint64_t pos = fromIndex; + + for (; pos < toIndex; pos+=2) { + int64_t val = area[pos]; + + if (val == key) return pos; + if (val > key) break; + } + + return encodeSearchMiss128(pos - 2); +} + +/** long low = 0; + long high = (toIndex - fromIndex)/sz - 1; + + while (high - low >= LINEAR_SEARCH_CUTOFF) { + long mid = (low + high) >>> 1; + long midVal = get(fromIndex + sz*mid); + + if (midVal < key) + low = mid + 1; + else if (midVal > key) + high = mid - 1; + else + return fromIndex + sz*mid; + } + + for (fromIndex += low*sz; fromIndex < toIndex; fromIndex+=sz) { + long val = get(fromIndex); + + if (val == key) return fromIndex; + if (val > key) return encodeSearchMiss(sz, fromIndex); + } + + return encodeSearchMiss(sz, toIndex - sz); */ + +int64_t ms_binary_search_128(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex) { + int64_t low = 0; + int64_t high = (toIndex - fromIndex) / 2 - 1; + + while (high - low >= 32) { + int64_t mid = low + (high - low) / 2; + int64_t midVal = area[fromIndex + mid * 2]; + + if (midVal < key) { + low = mid + 1; + } else if (midVal > key) { + high = mid - 1; + } else { + return fromIndex + mid * 2; + } + } + + for (fromIndex += low * 2; fromIndex < toIndex; fromIndex+=2) { + int64_t val = area[fromIndex]; + + if (val == key) return fromIndex; + if (val > key) return encodeSearchMiss128(fromIndex); + } + + return encodeSearchMiss128(toIndex - 2); +} + +int64_t ms_binary_search_64upper(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex) { + int64_t low = 0; + int64_t high = toIndex - fromIndex - 1; + + while (high - low > 32) { + int64_t mid = low + (high - low) / 2; + int64_t midVal = area[fromIndex + mid]; + + if (midVal < key) { + low = mid + 1; + } else if (midVal > key) { + high = mid - 1; + } else { + return fromIndex + mid; + } + } + + for (fromIndex += low; fromIndex < toIndex; fromIndex++) { + if (area[fromIndex] >= key) return fromIndex; + } + + return toIndex; +} diff --git a/code/libraries/array/cpp/src/main/public/cpphelpers.hpp b/code/libraries/array/cpp/src/main/public/cpphelpers.hpp new file mode 100644 index 00000000..2fa03fd9 --- /dev/null +++ b/code/libraries/array/cpp/src/main/public/cpphelpers.hpp @@ -0,0 +1,14 @@ +#include + +#pragma once + +extern "C" { + void ms_sort_64(int64_t* area, uint64_t start, uint64_t end); + void ms_sort_128(int64_t* area, uint64_t start, uint64_t end); + + int64_t ms_linear_search_64(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex); + int64_t ms_linear_search_128(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex); + + int64_t ms_binary_search_128(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex); + int64_t ms_binary_search_64upper(int64_t key, int64_t* area, uint64_t fromIndex, uint64_t toIndex); +} diff --git a/code/libraries/array/java/nu/marginalia/array/LongArray.java b/code/libraries/array/java/nu/marginalia/array/LongArray.java index 98059f9f..b597900c 100644 --- a/code/libraries/array/java/nu/marginalia/array/LongArray.java +++ b/code/libraries/array/java/nu/marginalia/array/LongArray.java @@ -12,6 +12,7 @@ import java.lang.foreign.Arena; public interface LongArray extends LongArrayBase, LongArrayTransformations, LongArraySearch, LongArraySort, AutoCloseable { int WORD_SIZE = 8; + int foo = 3; @Deprecated static LongArray allocate(long size) { diff --git a/code/libraries/array/java/nu/marginalia/array/algo/LongArrayBase.java b/code/libraries/array/java/nu/marginalia/array/algo/LongArrayBase.java index ab7f18bd..d70b7761 100644 --- a/code/libraries/array/java/nu/marginalia/array/algo/LongArrayBase.java +++ b/code/libraries/array/java/nu/marginalia/array/algo/LongArrayBase.java @@ -8,6 +8,7 @@ import java.nio.channels.FileChannel; import java.nio.file.Path; public interface LongArrayBase extends BulkTransferArray { + long get(long pos); void set(long pos, long value); @@ -26,6 +27,12 @@ public interface LongArrayBase extends BulkTransferArray { } } + void quickSortNative(long start, long end); + void quickSortNative128(long start, long end); + long linearSearchNative(long key, long start, long end); + long linearSearchNative128(long key, long start, long end); + long binarySearchNativeUB(long key, long start, long end); + long binarySearchNative128(long key, long start, long end); default void increment(long pos) { set(pos, get(pos) + 1); } diff --git a/code/libraries/array/java/nu/marginalia/array/algo/LongArraySearch.java b/code/libraries/array/java/nu/marginalia/array/algo/LongArraySearch.java index 5a2421e6..367062a6 100644 --- a/code/libraries/array/java/nu/marginalia/array/algo/LongArraySearch.java +++ b/code/libraries/array/java/nu/marginalia/array/algo/LongArraySearch.java @@ -1,5 +1,6 @@ package nu.marginalia.array.algo; +import nu.marginalia.NativeAlgos; import nu.marginalia.array.buffer.LongQueryBuffer; public interface LongArraySearch extends LongArrayBase { @@ -7,6 +8,38 @@ public interface LongArraySearch extends LongArrayBase { int LINEAR_SEARCH_CUTOFF = 32; default long linearSearch(long key, long fromIndex, long toIndex) { + if (NativeAlgos.isAvailable) { + return linearSearchNative(key, fromIndex, toIndex); + } else { + return linearSearchJava(key, fromIndex, toIndex); + } + } + + default long linearSearchN(int sz, long key, long fromIndex, long toIndex) { + if (NativeAlgos.isAvailable && sz == 2) { + return linearSearchNative128(key, fromIndex, toIndex); + } else { + return linearSearchNJava(sz, key, fromIndex, toIndex); + } + } + + default long binarySearchUpperBound(long key, long fromIndex, long toIndex) { + if (NativeAlgos.isAvailable) { + return binarySearchNativeUB(key, fromIndex, toIndex); + } else { + return binarySearchUpperBoundJava(key, fromIndex, toIndex); + } + } + + default long binarySearchN(int sz, long key, long fromIndex, long toIndex) { + if (NativeAlgos.isAvailable && sz == 2) { + return binarySearchNative128(key, fromIndex, toIndex); + } else { + return binarySearchNJava(sz, key, fromIndex, toIndex); + } + } + + default long linearSearchJava(long key, long fromIndex, long toIndex) { long pos; for (pos = fromIndex; pos < toIndex; pos++) { @@ -19,16 +52,7 @@ public interface LongArraySearch extends LongArrayBase { return encodeSearchMiss(1, pos - 1); } - default long linearSearchUpperBound(long key, long fromIndex, long toIndex) { - - for (long pos = fromIndex; pos < toIndex; pos++) { - if (get(pos) >= key) return pos; - } - - return toIndex; - } - - default long linearSearchN(int sz, long key, long fromIndex, long toIndex) { + default long linearSearchNJava(int sz, long key, long fromIndex, long toIndex) { long pos; for (pos = fromIndex; pos < toIndex; pos+=sz) { @@ -60,7 +84,7 @@ public interface LongArraySearch extends LongArrayBase { return linearSearch(key, fromIndex + low, fromIndex + high + 1); } - default long binarySearchN(int sz, long key, long fromIndex, long toIndex) { + default long binarySearchNJava(int sz, long key, long fromIndex, long toIndex) { long low = 0; long high = (toIndex - fromIndex)/sz - 1; @@ -87,7 +111,7 @@ public interface LongArraySearch extends LongArrayBase { } - default long binarySearchUpperBound(long key, long fromIndex, long toIndex) { + default long binarySearchUpperBoundJava(long key, long fromIndex, long toIndex) { long low = 0; long high = (toIndex - fromIndex) - 1; @@ -110,28 +134,6 @@ public interface LongArraySearch extends LongArrayBase { return toIndex; } - default long binarySearchUpperBoundN(int sz, long key, long fromIndex, long toIndex) { - long low = 0; - long high = (toIndex - fromIndex)/sz - 1; - - while (high - low >= LINEAR_SEARCH_CUTOFF) { - long mid = (low + high) >>> 1; - long midVal = get(fromIndex + sz*mid); - - if (midVal < key) - low = mid + 1; - else if (midVal > key) - high = mid - 1; - else - return fromIndex + sz*mid; - } - - for (fromIndex += low; fromIndex < toIndex; fromIndex+=sz) { - if (get(fromIndex) >= key) return fromIndex; - } - - return toIndex; - } default void retain(LongQueryBuffer buffer, long boundary, long searchStart, long searchEnd) { diff --git a/code/libraries/array/java/nu/marginalia/array/algo/LongArraySort.java b/code/libraries/array/java/nu/marginalia/array/algo/LongArraySort.java index 1c5b98f7..80a3d6d7 100644 --- a/code/libraries/array/java/nu/marginalia/array/algo/LongArraySort.java +++ b/code/libraries/array/java/nu/marginalia/array/algo/LongArraySort.java @@ -1,5 +1,7 @@ package nu.marginalia.array.algo; +import nu.marginalia.NativeAlgos; + import java.io.IOException; import java.nio.channels.FileChannel; import java.nio.file.Files; @@ -68,33 +70,6 @@ public interface LongArraySort extends LongArrayBase { return pos; } - default void sortLargeSpan(SortingContext ctx, long start, long end) throws IOException { - long size = end - start; - - if (size < ctx.memorySortLimit()) { - quickSort(start, end); - } - else { - mergeSort(start, end, ctx.tempDir()); - } - } - - default void sortLargeSpanN(SortingContext ctx, int sz, long start, long end) throws IOException { - if (sz == 1) { - sortLargeSpan(ctx, start, end); - return; - } - - long size = end - start; - - if (size < ctx.memorySortLimit()) { - quickSortN(sz, start, end); - } - else { - mergeSortN(sz, start, end, ctx.tempDir()); - } - } - default boolean isSortedN(int wordSize, long start, long end) { if (start == end) return true; @@ -109,8 +84,6 @@ public interface LongArraySort extends LongArrayBase { return true; } - - default void insertionSort(long start, long end) { SortAlgoInsertionSort._insertionSort(this, start, end); } @@ -119,11 +92,13 @@ public interface LongArraySort extends LongArrayBase { SortAlgoInsertionSort._insertionSortN(this, sz, start, end); } - default void quickSort(long start, long end) { if (end - start < 64) { insertionSort(start, end); } + else if (NativeAlgos.isAvailable) { + quickSortNative(start, end); + } else { SortAlgoQuickSort._quickSortLH(this, start, end - 1); } @@ -135,7 +110,12 @@ public interface LongArraySort extends LongArrayBase { if (end == start) return; - SortAlgoQuickSort._quickSortLHN(this, wordSize, start, end - wordSize); + if (NativeAlgos.isAvailable && wordSize == 2) { + quickSortNative128(start, end); + } + else { + SortAlgoQuickSort._quickSortLHN(this, wordSize, start, end - wordSize); + } } default void mergeSortN(int wordSize, long start, long end, Path tmpDir) throws IOException { diff --git a/code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplIntArrayDelegate.java b/code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplIntArrayDelegate.java deleted file mode 100644 index 8431cba7..00000000 --- a/code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplIntArrayDelegate.java +++ /dev/null @@ -1,53 +0,0 @@ -package nu.marginalia.array.delegate; - -import nu.marginalia.array.ArrayRangeReference; -import nu.marginalia.array.IntArray; - -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.Path; - -public class ReferenceImplIntArrayDelegate implements IntArray { - - private final IntArray delegate; - - public ReferenceImplIntArrayDelegate(IntArray delegate) { - this.delegate = delegate; - } - - @Override - public int get(long pos) { - return delegate.get(pos); - } - - @Override - public void set(long pos, int value) { - delegate.set(pos, value); - } - - @Override - public long size() { - return delegate.size(); - } - - @Override - public void write(Path file) throws IOException { - delegate.write(file); - } - - @Override - public void transferFrom(FileChannel source, long sourceStart, long arrayStart, long arrayEnd) throws IOException { - delegate.transferFrom(source, sourceStart, arrayStart, arrayEnd); - } - - @Override - public ArrayRangeReference directRangeIfPossible(long start, long end) { - return delegate.directRangeIfPossible(start, end); - } - - @Override - public void force() { - delegate.force(); - } - -} diff --git a/code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplLongArrayDelegate.java b/code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplLongArrayDelegate.java deleted file mode 100644 index cb8030bd..00000000 --- a/code/libraries/array/java/nu/marginalia/array/delegate/ReferenceImplLongArrayDelegate.java +++ /dev/null @@ -1,57 +0,0 @@ -package nu.marginalia.array.delegate; - -import nu.marginalia.array.ArrayRangeReference; -import nu.marginalia.array.LongArray; - -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.Path; - -public class ReferenceImplLongArrayDelegate implements LongArray { - - private final LongArray delegate; - - public ReferenceImplLongArrayDelegate(LongArray delegate) { - this.delegate = delegate; - } - - @Override - public long get(long pos) { - return delegate.get(pos); - } - - @Override - public void set(long pos, long value) { - delegate.set(pos, value); - } - - @Override - public long size() { - return delegate.size(); - } - - @Override - public void write(Path file) throws IOException { - delegate.write(file); - } - - @Override - public void transferFrom(FileChannel source, long sourceStart, long arrayStart, long arrayEnd) throws IOException { - delegate.transferFrom(source, sourceStart, arrayStart, arrayEnd); - } - - @Override - public ArrayRangeReference directRangeIfPossible(long start, long end) { - return delegate.directRangeIfPossible(start, end); - } - - @Override - public void force() { - delegate.force(); - } - - @Override - public void close() { - delegate.close(); - } -} diff --git a/code/libraries/array/java/nu/marginalia/array/delegate/ShiftedLongArray.java b/code/libraries/array/java/nu/marginalia/array/delegate/ShiftedLongArray.java index d0dd2958..627e1755 100644 --- a/code/libraries/array/java/nu/marginalia/array/delegate/ShiftedLongArray.java +++ b/code/libraries/array/java/nu/marginalia/array/delegate/ShiftedLongArray.java @@ -75,6 +75,35 @@ public class ShiftedLongArray implements LongArray { delegate.fill(start + shift, end + shift, val); } + @Override + public void quickSortNative(long start, long end) { + delegate.quickSortNative(start + shift, end + shift); + } + @Override + public void quickSortNative128(long start, long end) { + delegate.quickSortNative128(start, end); + } + + @Override + public long linearSearchNative(long key, long start, long end) { + return delegate.linearSearchNative(key, start + shift, end + shift); + } + + @Override + public long linearSearchNative128(long key, long start, long end) { + return delegate.linearSearchNative128(key, start, end); + } + + @Override + public long binarySearchNativeUB(long key, long start, long end) { + return delegate.binarySearchNativeUB(key, start + shift, end + shift); + } + + @Override + public long binarySearchNative128(long key, long start, long end) { + return delegate.binarySearchNative128(key, start, end); + } + @Override public long size() { return size; @@ -132,13 +161,6 @@ public class ShiftedLongArray implements LongArray { return delegate.isSortedN(sz, shift + start, shift + end); } - public void sortLargeSpanN(SortingContext ctx, int sz, long start, long end) throws IOException { - delegate.sortLargeSpanN(ctx, sz, start, end); - } - - public void sortLargeSpan(SortingContext ctx, long start, long end) throws IOException { - delegate.sortLargeSpan(ctx, start, end); - } public long searchN(int sz, long key) { if (size < 128) { @@ -216,14 +238,7 @@ public class ShiftedLongArray implements LongArray { public long binarySearchUpperBound(long key, long fromIndex, long toIndex) { return translateSearchResult(1, delegate.binarySearchUpperBound(key, fromIndex + shift, toIndex+shift)); } - @Override - public long linearSearchUpperBound(long key, long fromIndex, long toIndex) { - return translateSearchResult(1, delegate.linearSearchUpperBound(key, fromIndex + shift, toIndex+shift)); - } - @Override - public long binarySearchUpperBoundN(int sz, long key, long fromIndex, long toIndex) { - return translateSearchResult(sz, delegate.binarySearchUpperBoundN(sz, key, fromIndex + shift, toIndex+shift)); - } + private long translateSearchResult(int sz, long delegatedIdx) { long ret; diff --git a/code/libraries/array/java/nu/marginalia/array/page/SegmentLongArray.java b/code/libraries/array/java/nu/marginalia/array/page/SegmentLongArray.java index 5e3b8a59..4342c90e 100644 --- a/code/libraries/array/java/nu/marginalia/array/page/SegmentLongArray.java +++ b/code/libraries/array/java/nu/marginalia/array/page/SegmentLongArray.java @@ -1,5 +1,6 @@ package nu.marginalia.array.page; +import nu.marginalia.NativeAlgos; import nu.marginalia.array.ArrayRangeReference; import nu.marginalia.array.LongArray; @@ -125,6 +126,36 @@ public class SegmentLongArray implements PartitionPage, LongArray { return segment.byteSize() / JAVA_LONG.byteSize(); } + @Override + public void quickSortNative(long start, long end) { + NativeAlgos.sort(segment, start, end); + } + @Override + public void quickSortNative128(long start, long end) { + NativeAlgos.sort128(segment, start, end); + } + + @Override + public long linearSearchNative(long key, long start, long end) { + return NativeAlgos.linearSearch64(key, segment, start, end); + } + + @Override + public long linearSearchNative128(long key, long start, long end) { + return NativeAlgos.linearSearch128(key, segment, start, end); + } + + @Override + public long binarySearchNativeUB(long key, long start, long end) { + return NativeAlgos.binarySearch64Upper(key, segment, start, end); + } + + @Override + public long binarySearchNative128(long key, long start, long end) { + return NativeAlgos.binarySearch128(key, segment, start, end); + } + + @Override public ByteBuffer getByteBuffer() { return segment.asByteBuffer(); @@ -147,6 +178,7 @@ public class SegmentLongArray implements PartitionPage, LongArray { } } + public ArrayRangeReference directRangeIfPossible(long start, long end) { return new ArrayRangeReference<>(this, start, end); } diff --git a/code/libraries/array/java/nu/marginalia/array/page/UnsafeLongArray.java b/code/libraries/array/java/nu/marginalia/array/page/UnsafeLongArray.java index 4ef0da02..6cf58daa 100644 --- a/code/libraries/array/java/nu/marginalia/array/page/UnsafeLongArray.java +++ b/code/libraries/array/java/nu/marginalia/array/page/UnsafeLongArray.java @@ -1,5 +1,6 @@ package nu.marginalia.array.page; +import nu.marginalia.NativeAlgos; import nu.marginalia.array.ArrayRangeReference; import nu.marginalia.array.LongArray; import org.slf4j.Logger; @@ -273,4 +274,34 @@ public class UnsafeLongArray implements PartitionPage, LongArray { } } + @Override + public void quickSortNative(long start, long end) { + NativeAlgos.sort(segment, start, end); + } + + @Override + public void quickSortNative128(long start, long end) { + NativeAlgos.sort128(segment, start, end); + } + + @Override + public long linearSearchNative(long key, long start, long end) { + return NativeAlgos.linearSearch64(key, segment, start, end); + } + + @Override + public long linearSearchNative128(long key, long start, long end) { + return NativeAlgos.linearSearch128(key, segment, start, end); + } + + @Override + public long binarySearchNativeUB(long key, long start, long end) { + return NativeAlgos.binarySearch64Upper(key, segment, start, end); + } + + @Override + public long binarySearchNative128(long key, long start, long end) { + return NativeAlgos.binarySearch128(key, segment, start, end); + } + } diff --git a/code/libraries/array/src/jmh/java/nu/marginalia/array/SimulatedNaiveArray.java b/code/libraries/array/src/jmh/java/nu/marginalia/array/SimulatedNaiveArray.java deleted file mode 100644 index 70aacd48..00000000 --- a/code/libraries/array/src/jmh/java/nu/marginalia/array/SimulatedNaiveArray.java +++ /dev/null @@ -1,53 +0,0 @@ -package nu.marginalia.array; - -import java.io.IOException; -import java.nio.LongBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.Path; - -/** This is a benchmark comparison implementation of LongArray that - * does not include the optimizations in PagingLongArray */ -public class SimulatedNaiveArray implements LongArray { - final LongBuffer[] buffers; - final int bufferSize; - - public SimulatedNaiveArray(int size, int bufferSize) { - this.bufferSize = bufferSize; - buffers = new LongBuffer[size / bufferSize]; - for (int i = 0 ; i < buffers.length; i++) { - buffers[i] = LongBuffer.allocate(bufferSize); - } - } - - @Override - public ArrayRangeReference directRangeIfPossible(long start, long end) { - return null; - } - - @Override - public void force() {} - - @Override - public void close() { - } - - @Override - public long get(long pos) { - return buffers[(int) pos/bufferSize].get((int) pos%bufferSize); - } - - @Override - public void set(long pos, long value) { - buffers[(int) pos/bufferSize].put((int) pos%bufferSize, value); - } - - @Override - public long size() { - return 1024; - } - - @Override - public void write(Path file) throws IOException {} - @Override - public void transferFrom(FileChannel source, long sourceStart, long arrayStart, long arrayEnd) throws IOException {} -} \ No newline at end of file diff --git a/code/libraries/array/src/jmh/java/nu/marginalia/array/page/FoldBenchmark.java b/code/libraries/array/src/jmh/java/nu/marginalia/array/page/FoldBenchmark.java deleted file mode 100644 index 50f6e925..00000000 --- a/code/libraries/array/src/jmh/java/nu/marginalia/array/page/FoldBenchmark.java +++ /dev/null @@ -1,64 +0,0 @@ -package nu.marginalia.array.page; - -import nu.marginalia.array.LongArray; -import nu.marginalia.array.SimulatedNaiveArray; -import org.openjdk.jmh.annotations.*; - -import java.lang.foreign.Arena; - -public class FoldBenchmark { - - @State(Scope.Benchmark) - public static class BenchState { - - @Setup(Level.Trial) - public void doSetup() { - array.transformEach(0, size, (pos,old) -> ~pos); - array2.transformEach(0, size, (pos,old) -> ~pos); - array3.transformEach(0, size, (pos,old) -> ~pos); - simulateNaiveApproach.transformEach(0, size, (pos,old) -> ~pos); - } - - int size = 100*1024*1024; - int pageSize = 10*1024; - LongArray array = LongArray.allocate(size); - LongArray array2 = SegmentLongArray.onHeap(Arena.ofShared(), size); - LongArray array3 = SegmentLongArray.onHeap(Arena.ofConfined(), size); - LongArray simulateNaiveApproach = new SimulatedNaiveArray(size, pageSize); - } - - @Fork(value = 1, warmups = 1) - @Warmup(iterations = 1) - @Benchmark - @BenchmarkMode(Mode.Throughput) - public long benchArrayFoldGoldStandard(BenchState state) { - return state.array.fold(0, 0, state.size, Long::sum); - } - - @Fork(value = 1, warmups = 1) - @Warmup(iterations = 1) - @Benchmark - @BenchmarkMode(Mode.Throughput) - public long benchArrayFoldGoldStandard2(BenchState state) { - return state.array2.fold(0, 0, state.size, Long::sum); - } - - @Fork(value = 1, warmups = 1) - @Warmup(iterations = 1) - @Benchmark - @BenchmarkMode(Mode.Throughput) - public long benchArrayFoldGoldStandard3(BenchState state) { - return state.array3.fold(0, 0, state.size, Long::sum); - } - - - - @Fork(value = 1, warmups = 1) - @Warmup(iterations = 1) - @Benchmark - @BenchmarkMode(Mode.Throughput) - public long benchArrayFoldNaive(BenchState state) { - return state.simulateNaiveApproach.fold(0, 0, state.size, Long::sum); - } - -} diff --git a/code/libraries/array/src/jmh/java/nu/marginalia/array/page/QuicksortBenchmark.java b/code/libraries/array/src/jmh/java/nu/marginalia/array/page/QuicksortBenchmark.java index e90777d8..084c8024 100644 --- a/code/libraries/array/src/jmh/java/nu/marginalia/array/page/QuicksortBenchmark.java +++ b/code/libraries/array/src/jmh/java/nu/marginalia/array/page/QuicksortBenchmark.java @@ -1,83 +1,46 @@ package nu.marginalia.array.page; import nu.marginalia.array.LongArray; -import nu.marginalia.array.SimulatedNaiveArray; +import nu.marginalia.array.LongArrayFactory; import org.openjdk.jmh.annotations.*; -import java.lang.foreign.Arena; - /** This benchmark simulates the sorting in index creation */ public class QuicksortBenchmark { @State(Scope.Benchmark) public static class BenchState { - @Setup(Level.Trial) + @Setup(Level.Invocation) public void doSetup() { array.transformEach(0, size, (pos,old) -> ~pos); - array2.transformEach(0, size, (pos,old) -> ~pos); - array3.transformEach(0, size, (pos,old) -> ~pos); - simulateNaiveApproach.transformEach(0, size, (pos,old) -> ~pos); } - int size = 100*1024*1024; + int size = 1024*1024; int pageSize = 10*1024; - LongArray array = LongArray.allocate(size); - LongArray array2 = SegmentLongArray.onHeap(Arena.ofShared(), size); - LongArray array3 = SegmentLongArray.onHeap(Arena.ofConfined(), size); - LongArray simulateNaiveApproach = new SimulatedNaiveArray(size, pageSize); + LongArray array = LongArrayFactory.onHeapShared(size); } - @Fork(value = 1, warmups = 1) + @Fork(value = 2, warmups = 5) @Warmup(iterations = 1) @Benchmark @BenchmarkMode(Mode.Throughput) - public LongArray benchArrayFoldGoldStandard(BenchState state) { + public LongArray javaSort(BenchState state) { var array = state.array; - for (int i = 0; i + 100 < state.size; i+=100) { - array.quickSort(i, i + 100); - } - - return array; - } - @Fork(value = 1, warmups = 1) - @Warmup(iterations = 1) - @Benchmark - @BenchmarkMode(Mode.Throughput) - public LongArray benchArrayFoldGoldStandard2(BenchState state) { - var array = state.array2; - - for (int i = 0; i + 100 < state.size; i+=100) { - array.quickSort(i, i + 100); - } - - return array; - } - @Fork(value = 1, warmups = 1) - @Warmup(iterations = 1) - @Benchmark - @BenchmarkMode(Mode.Throughput) - public LongArray benchArrayFoldGoldStandard3(BenchState state) { - var array = state.array3; - - for (int i = 0; i + 100 < state.size; i+=100) { - array.quickSort(i, i + 100); - } + array.quickSortN(2, 0, array.size()); return array; } - @Fork(value = 1, warmups = 1) + @Fork(value = 2, warmups = 5) @Warmup(iterations = 1) @Benchmark @BenchmarkMode(Mode.Throughput) - public LongArray benchArrayFoldNaive(BenchState state) { - var array = state.simulateNaiveApproach; + public LongArray cppSort(BenchState state) { - for (int i = 0; i + 100 < state.size; i+=100) { - array.quickSort(i, i + 100); - } + var array = state.array; + + array.quickSortNative128(0, array.size()); return array; } diff --git a/code/libraries/array/test/nu/marginalia/array/algo/LongArraySearchTest.java b/code/libraries/array/test/nu/marginalia/array/algo/LongArraySearchTest.java index fa50045e..f320d4bc 100644 --- a/code/libraries/array/test/nu/marginalia/array/algo/LongArraySearchTest.java +++ b/code/libraries/array/test/nu/marginalia/array/algo/LongArraySearchTest.java @@ -47,20 +47,19 @@ class LongArraySearchTest { } @Test - void linearSearchUpperBound() { - linearSearchUpperBoundTester(basicArray); - linearSearchUpperBoundTester(shiftedArray); - linearSearchUpperBoundTester(segmentArray); + void binarySearchUpperBoundNative() { + binarySearchUpperBoundNativeTester(basicArray); + binarySearchUpperBoundNativeTester(shiftedArray); + binarySearchUpperBoundNativeTester(segmentArray); } + @Test public void testEmptyRange() { assertTrue(segmentArray.binarySearchN(2, 0, 0, 0) < 0); assertTrue(segmentArray.linearSearchN(2, 0, 0, 0) < 0); assertTrue(segmentArray.binarySearch(0, 0, 0) < 0); assertTrue(segmentArray.linearSearch(0, 0, 0) < 0); - - assertEquals(0, segmentArray.linearSearchUpperBound(0, 0, 0)); } void linearSearchTester(LongArray array) { @@ -112,12 +111,10 @@ class LongArraySearchTest { } } } - void linearSearchUpperBoundTester(LongArray array) { - for (int i = 0; i < array.size() * 3; i++) { - long ret = array.linearSearchUpperBound(i, 0, array.size()); - long ret2 = array.binarySearchUpperBound(i, 0, array.size()); - assertEquals(ret, ret2); + void binarySearchUpperBoundNativeTester(LongArray array) { + for (int i = 0; i < array.size() * 3; i++) { + long ret = array.binarySearchNativeUB(i, 0, array.size()); if ((i % 3) == 0) { assertTrue(ret >= 0); @@ -125,13 +122,11 @@ class LongArraySearchTest { } else { if (i > 0 && ret > 0 && ret < array.size()) { - System.out.println(ret); assertTrue(array.get(ret-1) < i); } } } } - @Test void retain() { long[] vals = new long[128]; diff --git a/code/libraries/array/test/nu/marginalia/array/algo/LongArraySortTest.java b/code/libraries/array/test/nu/marginalia/array/algo/LongArraySortTest.java index 81916b61..2024e105 100644 --- a/code/libraries/array/test/nu/marginalia/array/algo/LongArraySortTest.java +++ b/code/libraries/array/test/nu/marginalia/array/algo/LongArraySortTest.java @@ -61,12 +61,40 @@ class LongArraySortTest { void sort(LongArray array, long start, long end) throws IOException; } + @Test + public void testNative() { + LongArray array = LongArray.allocate(4); + array.set(0, 1); + array.set(1, 4); + array.set(2, 3); + array.set(3, 2); + array.quickSortNative(0, 4); + assertTrue(array.isSorted(0, 4)); + assertEquals(1, array.get(0)); + assertEquals(2, array.get(1)); + assertEquals(3, array.get(2)); + assertEquals(4, array.get(3)); + + array.set(2, 5); + array.quickSortNative(2, 4); + + assertEquals(4, array.get(2)); + assertEquals(5, array.get(3)); + + assertTrue(array.isSorted(2, 4)); + } @Test public void quickSortStressTest() throws IOException { LongArray array = LongArray.allocate(65536); sortAlgorithmTester(array, LongArraySort::quickSort); } + @Test + public void nativeSortTest() throws IOException { + LongArray array = LongArray.allocate(65536); + sortAlgorithmTester(array, LongArraySort::quickSortNative); + } + @Test public void insertionSortStressTest() throws IOException { diff --git a/settings.gradle b/settings.gradle index 13622d9c..8d29fb02 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,7 @@ include 'code:index:index-forward' include 'code:index:index-reverse' include 'code:libraries:array' +include 'code:libraries:array:cpp' include 'code:libraries:geo-ip' include 'code:libraries:btree' include 'code:libraries:easy-lsh'