mirror of
https://github.com/MarginaliaSearch/MarginaliaSearch.git
synced 2025-02-23 04:58:59 +00:00
(array, experimental) Call C++ helper methods to do some low level stuff a bit faster than is possible with Java
This commit is contained in:
parent
c837321df1
commit
55a7c1db00
@ -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
|
||||
|
26
code/libraries/array/cpp/build.gradle
Normal file
26
code/libraries/array/cpp/build.gradle
Normal file
@ -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')
|
8
code/libraries/array/cpp/compile.sh
Normal file
8
code/libraries/array/cpp/compile.sh
Normal file
@ -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
|
133
code/libraries/array/cpp/java/nu/marginalia/NativeAlgos.java
Normal file
133
code/libraries/array/cpp/java/nu/marginalia/NativeAlgos.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
1
code/libraries/array/cpp/resources/.gitignore
vendored
Normal file
1
code/libraries/array/cpp/resources/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
../../resources/cpp/libcpp.so
|
136
code/libraries/array/cpp/src/main/cpp/cpphelpers.cpp
Normal file
136
code/libraries/array/cpp/src/main/cpp/cpphelpers.cpp
Normal file
@ -0,0 +1,136 @@
|
||||
#include "cpphelpers.hpp"
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
|
||||
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;
|
||||
}
|
14
code/libraries/array/cpp/src/main/public/cpphelpers.hpp
Normal file
14
code/libraries/array/cpp/src/main/public/cpphelpers.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#include <stdint.h>
|
||||
|
||||
#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);
|
||||
}
|
@ -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) {
|
||||
|
@ -8,6 +8,7 @@ import java.nio.channels.FileChannel;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface LongArrayBase extends BulkTransferArray<LongBuffer> {
|
||||
|
||||
long get(long pos);
|
||||
|
||||
void set(long pos, long value);
|
||||
@ -26,6 +27,12 @@ public interface LongArrayBase extends BulkTransferArray<LongBuffer> {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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,8 +110,13 @@ public interface LongArraySort extends LongArrayBase {
|
||||
if (end == start)
|
||||
return;
|
||||
|
||||
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 {
|
||||
int length = (int) (end - start);
|
||||
|
@ -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<IntArray> directRangeIfPossible(long start, long end) {
|
||||
return delegate.directRangeIfPossible(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void force() {
|
||||
delegate.force();
|
||||
}
|
||||
|
||||
}
|
@ -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<LongArray> directRangeIfPossible(long start, long end) {
|
||||
return delegate.directRangeIfPossible(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void force() {
|
||||
delegate.force();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
delegate.close();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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<LongArray> directRangeIfPossible(long start, long end) {
|
||||
return new ArrayRangeReference<>(this, start, end);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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<LongArray> 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 {}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
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 benchArrayFoldGoldStandard2(BenchState state) {
|
||||
var array = state.array2;
|
||||
public LongArray cppSort(BenchState state) {
|
||||
|
||||
for (int i = 0; i + 100 < state.size; i+=100) {
|
||||
array.quickSort(i, i + 100);
|
||||
}
|
||||
var array = state.array;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
@Fork(value = 1, warmups = 1)
|
||||
@Warmup(iterations = 1)
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public LongArray benchArrayFoldNaive(BenchState state) {
|
||||
var array = state.simulateNaiveApproach;
|
||||
|
||||
for (int i = 0; i + 100 < state.size; i+=100) {
|
||||
array.quickSort(i, i + 100);
|
||||
}
|
||||
array.quickSortNative128(0, array.size());
|
||||
|
||||
return array;
|
||||
}
|
||||
|
@ -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];
|
||||
|
@ -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 {
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user