(array) Add sun.misc.Unsafe variant of LongArray

This commit is contained in:
Viktor Lofgren 2024-01-22 13:38:42 +01:00
parent 40c9d2050f
commit 1eb0adf6d3
4 changed files with 207 additions and 9 deletions

View File

@ -6,6 +6,7 @@ import nu.marginalia.array.algo.LongArraySort;
import nu.marginalia.array.algo.LongArrayTransformations; import nu.marginalia.array.algo.LongArrayTransformations;
import nu.marginalia.array.delegate.ShiftedLongArray; import nu.marginalia.array.delegate.ShiftedLongArray;
import nu.marginalia.array.page.SegmentLongArray; import nu.marginalia.array.page.SegmentLongArray;
import nu.marginalia.array.page.UnsafeLongArray;
import java.lang.foreign.Arena; import java.lang.foreign.Arena;
@ -15,7 +16,7 @@ public interface LongArray extends LongArrayBase, LongArrayTransformations, Long
@Deprecated @Deprecated
static LongArray allocate(long size) { static LongArray allocate(long size) {
return SegmentLongArray.onHeap(Arena.ofShared(), size); return UnsafeLongArray.onHeap(Arena.ofShared(), size);
} }
default LongArray shifted(long offset) { default LongArray shifted(long offset) {

View File

@ -1,6 +1,7 @@
package nu.marginalia.array; package nu.marginalia.array;
import nu.marginalia.array.page.SegmentLongArray; import nu.marginalia.array.page.SegmentLongArray;
import nu.marginalia.array.page.UnsafeLongArray;
import java.io.IOException; import java.io.IOException;
import java.lang.foreign.Arena; import java.lang.foreign.Arena;
@ -9,43 +10,43 @@ import java.nio.file.Path;
public class LongArrayFactory { public class LongArrayFactory {
public static LongArray onHeapConfined(long size) { public static LongArray onHeapConfined(long size) {
return SegmentLongArray.onHeap(Arena.ofConfined(), size); return UnsafeLongArray.onHeap(Arena.ofConfined(), size);
} }
public static LongArray onHeapShared(long size) { public static LongArray onHeapShared(long size) {
return SegmentLongArray.onHeap(Arena.ofShared(), size); return UnsafeLongArray.onHeap(Arena.ofShared(), size);
} }
public static LongArray mmapForReadingConfined(Path filename) throws IOException { public static LongArray mmapForReadingConfined(Path filename) throws IOException {
return SegmentLongArray.fromMmapReadOnly(Arena.ofConfined(), filename, return UnsafeLongArray.fromMmapReadOnly(Arena.ofConfined(), filename,
0, 0,
Files.size(filename) / 8); Files.size(filename) / 8);
} }
public static LongArray mmapForReadingShared(Path filename) throws IOException { public static LongArray mmapForReadingShared(Path filename) throws IOException {
return SegmentLongArray.fromMmapReadOnly(Arena.ofShared(), filename, return UnsafeLongArray.fromMmapReadOnly(Arena.ofShared(), filename,
0, 0,
Files.size(filename) / 8); Files.size(filename) / 8);
} }
public static LongArray mmapForModifyingConfined(Path filename) throws IOException { public static LongArray mmapForModifyingConfined(Path filename) throws IOException {
return SegmentLongArray.fromMmapReadWrite(Arena.ofConfined(), filename, return UnsafeLongArray.fromMmapReadWrite(Arena.ofConfined(), filename,
0, Files.size(filename)); 0, Files.size(filename));
} }
public static LongArray mmapForModifyingShared(Path filename) throws IOException { public static LongArray mmapForModifyingShared(Path filename) throws IOException {
return SegmentLongArray.fromMmapReadWrite(Arena.ofShared(), filename, return UnsafeLongArray.fromMmapReadWrite(Arena.ofShared(), filename,
0, 0,
Files.size(filename) / 8); Files.size(filename) / 8);
} }
public static LongArray mmapForWritingConfined(Path filename, long size) throws IOException { public static LongArray mmapForWritingConfined(Path filename, long size) throws IOException {
return SegmentLongArray.fromMmapReadWrite(Arena.ofConfined(), filename, return UnsafeLongArray.fromMmapReadWrite(Arena.ofConfined(), filename,
0, size); 0, size);
} }
public static LongArray mmapForWritingShared(Path filename, long size) throws IOException { public static LongArray mmapForWritingShared(Path filename, long size) throws IOException {
return SegmentLongArray.fromMmapReadWrite(Arena.ofShared(), filename, return UnsafeLongArray.fromMmapReadWrite(Arena.ofShared(), filename,
0, size); 0, size);
} }
} }

View File

@ -0,0 +1,181 @@
package nu.marginalia.array.page;
import nu.marginalia.array.ArrayRangeReference;
import nu.marginalia.array.LongArray;
import sun.misc.Unsafe;
import javax.annotation.Nullable;
import java.io.IOException;
import java.lang.foreign.Arena;
import java.lang.foreign.MemorySegment;
import java.nio.ByteBuffer;
import java.nio.LongBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
/** Variant of SegmentLongArray that uses Unsafe to access the memory.
* */
public class UnsafeLongArray implements PartitionPage, LongArray {
private static final Unsafe unsafe = UnsafeProvider.getUnsafe();
@Nullable
private final Arena arena;
private final MemorySegment segment;
private boolean closed;
UnsafeLongArray(MemorySegment segment,
@Nullable Arena arena) {
this.segment = segment;
this.arena = arena;
}
public static UnsafeLongArray onHeap(Arena arena, long size) {
return new UnsafeLongArray(arena.allocate(WORD_SIZE*size, 8), arena);
}
public static UnsafeLongArray fromMmapReadOnly(Arena arena, Path file, long offset, long size) throws IOException {
return new UnsafeLongArray(
mmapFile(arena, file, offset, size, FileChannel.MapMode.READ_ONLY, StandardOpenOption.READ),
arena);
}
public static UnsafeLongArray fromMmapReadWrite(Arena arena, Path file, long offset, long size) throws IOException {
return new UnsafeLongArray(
mmapFile(arena, file, offset, size, FileChannel.MapMode.READ_WRITE,
StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE),
arena);
}
private static MemorySegment mmapFile(Arena arena,
Path file,
long offset,
long size,
FileChannel.MapMode mode,
OpenOption... openOptions) throws IOException
{
try (var channel = (FileChannel) Files.newByteChannel(file, openOptions)) {
return channel.map(mode,
JAVA_LONG.byteSize() * offset,
JAVA_LONG.byteSize() * size,
arena);
}
catch (IOException ex) {
throw new IOException("Failed to map file " + file + " (" + offset + ":" + size + ")", ex);
}
}
@Override
public LongArray range(long start, long end) {
return new UnsafeLongArray(
segment.asSlice(
start * JAVA_LONG.byteSize(),
(end-start) * JAVA_LONG.byteSize()),
null);
}
@Override
public LongArray shifted(long start) {
return new UnsafeLongArray(
segment.asSlice(start * JAVA_LONG.byteSize()),
null);
}
@Override
public long get(long at) {
try {
return unsafe.getLong(segment.address() + at * JAVA_LONG.byteSize());
}
catch (IndexOutOfBoundsException ex) {
throw new IndexOutOfBoundsException("@" + at + "(" + 0 + ":" + segment.byteSize()/8 + ")");
}
}
@Override
public void get(long start, long end, long[] buffer) {
for (int i = 0; i < end - start; i++) {
buffer[i] = unsafe.getLong(segment.address() + (start + i) * JAVA_LONG.byteSize());
}
}
@Override
public void set(long at, long val) {
unsafe.putLong(segment.address() + at * JAVA_LONG.byteSize(), val);
}
@Override
public void set(long start, long end, LongBuffer buffer, int bufferStart) {
for (int i = 0; i < end - start; i++) {
unsafe.putLong(segment.address() + (start + i) * JAVA_LONG.byteSize(), buffer.get(bufferStart + i));
}
}
@Override
public synchronized void close() {
if (arena != null && !closed) {
arena.close();
}
closed = true;
}
@Override
public long size() {
return segment.byteSize() / JAVA_LONG.byteSize();
}
@Override
public ByteBuffer getByteBuffer() {
return segment.asByteBuffer();
}
@Override
public void write(Path filename) throws IOException {
try (var arena = Arena.ofConfined()) {
var destSegment = UnsafeLongArray.fromMmapReadWrite(arena, filename, 0, segment.byteSize());
destSegment.segment.copyFrom(segment);
destSegment.force();
}
}
@Override
public void force() {
if (segment.isMapped()) {
segment.force();
}
}
public ArrayRangeReference<LongArray> directRangeIfPossible(long start, long end) {
return new ArrayRangeReference<>(this, start, end);
}
@Override
public void transferFrom(FileChannel source, long sourceStart, long arrayStart, long arrayEnd) throws IOException {
final int stride = 1024*1204*128; // Copy 1 GB at a time 'cause byte buffers are 'a byte buffering
long ss = sourceStart;
for (long as = arrayStart; as < arrayEnd; as += stride, ss += stride) {
long ae = Math.min(as + stride, arrayEnd);
long index = as * JAVA_LONG.byteSize();
long length = (ae - as) * JAVA_LONG.byteSize();
var bufferSlice = segment.asSlice(index, length).asByteBuffer();
long startPos = ss * JAVA_LONG.byteSize();
while (bufferSlice.position() < bufferSlice.capacity()) {
source.read(bufferSlice, startPos + bufferSlice.position());
}
}
}
}

View File

@ -0,0 +1,15 @@
package nu.marginalia.array.page;
import sun.misc.Unsafe;
public class UnsafeProvider {
public static Unsafe getUnsafe() {
try {
var field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}