(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:
Viktor Lofgren 2024-05-13 18:38:43 +02:00
parent c837321df1
commit 55a7c1db00
22 changed files with 518 additions and 369 deletions

View File

@ -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

View 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')

View 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

View 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);
}
}
}

View File

@ -0,0 +1 @@
../../resources/cpp/libcpp.so

View 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;
}

View 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);
}

View File

@ -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) {

View File

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

View File

@ -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) {

View File

@ -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);

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

@ -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 {}
}

View File

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

View File

@ -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;
}

View File

@ -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];

View File

@ -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 {

View File

@ -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'