Skip to content

Commit e287eb0

Browse files
authored
Add a memory-mapped RandomAccessReader using MemorySegment api (#296)
* Add a memory-mapped RandomAccessReader using MemorySegment api * Use MemorySegmentReaderSupplier in ReaderSupplierFactory
1 parent 4443227 commit e287eb0

File tree

6 files changed

+405
-1
lines changed

6 files changed

+405
-1
lines changed

jvector-examples/pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,7 @@
214214
</build>
215215
</profile>
216216
<profile>
217-
<id>jdk21</id>
217+
<id>jdk22</id>
218218
<activation>
219219
<activeByDefault>true</activeByDefault>
220220
</activation>

jvector-examples/src/main/java/io/github/jbellis/jvector/example/util/ReaderSupplierFactory.java

+11
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,26 @@
1919
import io.github.jbellis.jvector.disk.SimpleMappedReaderSupplier;
2020

2121
import java.io.IOException;
22+
import java.lang.reflect.Constructor;
2223
import java.nio.file.Files;
2324
import java.nio.file.Path;
2425
import java.util.logging.Level;
2526
import java.util.logging.Logger;
2627

2728
public class ReaderSupplierFactory {
2829
private static final Logger LOG = Logger.getLogger(ReaderSupplierFactory.class.getName());
30+
private static final String MEMORY_SEGMENT_READER_CLASSNAME = "io.github.jbellis.jvector.disk.MemorySegmentReaderSupplier";
2931

3032
public static ReaderSupplier open(Path path) throws IOException {
33+
try {
34+
var supplierClass = Class.forName(MEMORY_SEGMENT_READER_CLASSNAME);
35+
Constructor<?> ctor = supplierClass.getConstructor(Path.class);
36+
return (ReaderSupplier) ctor.newInstance(path);
37+
} catch (Exception e) {
38+
LOG.log(Level.WARNING, "MemorySegmentReaderSupplier not available, falling back to MMapReaderSupplier. Reason: {0}: {1}",
39+
new Object[]{e.getClass().getName(), e.getMessage()});
40+
}
41+
3142
try {
3243
return new MMapReaderSupplier(path);
3344
} catch (UnsatisfiedLinkError|NoClassDefFoundError e) {

jvector-native/pom.xml

+8
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,14 @@
3535
</nonFilteredFileExtensions>
3636
</configuration>
3737
</plugin>
38+
<plugin>
39+
<groupId>org.apache.maven.plugins</groupId>
40+
<artifactId>maven-surefire-plugin</artifactId>
41+
<version>3.1.2</version>
42+
<configuration>
43+
<skip>false</skip>
44+
</configuration>
45+
</plugin>
3846
</plugins>
3947
</build>
4048
<profiles>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* Copyright DataStax, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.github.jbellis.jvector.disk;
18+
19+
import java.io.IOException;
20+
import java.lang.foreign.Arena;
21+
import java.lang.foreign.MemorySegment;
22+
import java.lang.foreign.ValueLayout;
23+
import java.lang.foreign.ValueLayout.OfFloat;
24+
import java.lang.foreign.ValueLayout.OfInt;
25+
import java.lang.foreign.ValueLayout.OfLong;
26+
import java.nio.ByteBuffer;
27+
import java.nio.ByteOrder;
28+
import java.nio.channels.FileChannel;
29+
import java.nio.channels.FileChannel.MapMode;
30+
import java.nio.file.Path;
31+
import java.nio.file.StandardOpenOption;
32+
33+
/**
34+
* {@link MemorySegment} based implementation of RandomAccessReader.
35+
* MemorySegmentReader doesn't have 2GB file size limitation of {@link SimpleMappedReader}.
36+
*/
37+
public class MemorySegmentReader implements RandomAccessReader {
38+
39+
private static final OfInt intLayout = ValueLayout.JAVA_INT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN);
40+
private static final OfFloat floatLayout = ValueLayout.JAVA_FLOAT_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN);
41+
private static final OfLong longLayout = ValueLayout.JAVA_LONG_UNALIGNED.withOrder(ByteOrder.BIG_ENDIAN);
42+
43+
final Arena arena;
44+
final MemorySegment memory;
45+
private long position = 0;
46+
47+
public MemorySegmentReader(Path path) throws IOException {
48+
arena = Arena.ofShared();
49+
try (var ch = FileChannel.open(path, StandardOpenOption.READ)) {
50+
memory = ch.map(MapMode.READ_ONLY, 0L, ch.size(), arena);
51+
} catch (Exception e) {
52+
arena.close();
53+
throw e;
54+
}
55+
}
56+
57+
MemorySegmentReader(Arena arena, MemorySegment memory) {
58+
this.arena = arena;
59+
this.memory = memory;
60+
}
61+
62+
@Override
63+
public void seek(long offset) {
64+
this.position = offset;
65+
}
66+
67+
@Override
68+
public long getPosition() {
69+
return position;
70+
}
71+
72+
@Override
73+
public void readFully(float[] buffer) {
74+
MemorySegment.copy(memory, floatLayout, position, buffer, 0, buffer.length);
75+
position += buffer.length * 4L;
76+
}
77+
78+
@Override
79+
public void readFully(byte[] b) {
80+
MemorySegment.copy(memory, ValueLayout.JAVA_BYTE, position, b, 0, b.length);
81+
position += b.length;
82+
}
83+
84+
@Override
85+
public void readFully(ByteBuffer buffer) {
86+
var remaining = buffer.remaining();
87+
var slice = memory.asSlice(position, remaining).asByteBuffer();
88+
buffer.put(slice);
89+
position += remaining;
90+
}
91+
92+
@Override
93+
public void readFully(long[] vector) {
94+
MemorySegment.copy(memory, longLayout, position, vector, 0, vector.length);
95+
position += vector.length * 8L;
96+
}
97+
98+
@Override
99+
public int readInt() {
100+
var k = memory.get(intLayout, position);
101+
position += 4;
102+
return k;
103+
}
104+
105+
@Override
106+
public float readFloat() {
107+
var f = memory.get(floatLayout, position);
108+
position += 4;
109+
return f;
110+
}
111+
112+
@Override
113+
public void read(int[] ints, int offset, int count) {
114+
MemorySegment.copy(memory, intLayout, position, ints, offset, count);
115+
position += count * 4L;
116+
}
117+
118+
@Override
119+
public void read(float[] floats, int offset, int count) {
120+
MemorySegment.copy(memory, floatLayout, position, floats, offset, count);
121+
position += count * 4L;
122+
}
123+
124+
/**
125+
* Loads the contents of the mapped segment into physical memory.
126+
* This is a best-effort mechanism.
127+
*/
128+
public void loadMemory() {
129+
memory.load();
130+
}
131+
132+
@Override
133+
public void close() {
134+
arena.close();
135+
}
136+
137+
/**
138+
* Creates a shallow copy of the MemorySegmentReader.
139+
* Underlying memory-mapped region will be shared between all copies.
140+
* When MemorySegmentReader is closed, all copies will become invalid.
141+
*/
142+
public MemorySegmentReader duplicate() {
143+
return new MemorySegmentReader(arena, memory);
144+
}
145+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright DataStax, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.github.jbellis.jvector.disk;
17+
18+
import java.io.IOException;
19+
import java.lang.foreign.Arena;
20+
import java.lang.foreign.MemorySegment;
21+
import java.nio.file.Path;
22+
23+
public class MemorySegmentReaderSupplier implements ReaderSupplier {
24+
private final InternalMemorySegmentReader reader;
25+
26+
public MemorySegmentReaderSupplier(Path path) throws IOException {
27+
reader = new InternalMemorySegmentReader(path);
28+
}
29+
30+
@Override
31+
public RandomAccessReader get() {
32+
return reader.duplicate();
33+
}
34+
35+
@Override
36+
public void close() {
37+
reader.close();
38+
}
39+
40+
private static class InternalMemorySegmentReader extends MemorySegmentReader {
41+
42+
private final boolean shouldClose;
43+
44+
private InternalMemorySegmentReader(Path path) throws IOException {
45+
super(path);
46+
shouldClose = true;
47+
}
48+
49+
private InternalMemorySegmentReader(Arena arena, MemorySegment memory) {
50+
super(arena, memory);
51+
shouldClose = false;
52+
}
53+
54+
@Override
55+
public void close() {
56+
if (shouldClose) {
57+
super.close();
58+
}
59+
}
60+
61+
@Override
62+
public InternalMemorySegmentReader duplicate() {
63+
return new InternalMemorySegmentReader(arena, memory);
64+
}
65+
}
66+
}

0 commit comments

Comments
 (0)