(array) Add sun.misc.Unsafe variant of LongArray
This commit is contained in:
parent
40c9d2050f
commit
1eb0adf6d3
@ -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) {
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user