Skip to content

Commit ecea00e

Browse files
authored
[Java] Refactor SearchResults implementation classes (#1067)
This PR refactors the `SearchResults` implementation classes, by removing inheritance and replacing it with factory methods to create results from native data. The current implementation classes use inheritance and polymorphism just during creation (in the ctors); this is confusing as it forces to navigate across derived/base class to understand the mechanism, and forces the base class to hold onto references to MemorySegments -- something we don't need to do. This is preliminary work towards #1037, as it makes the lifetime of those MemorySegment clearer. Authors: - Lorenzo Dematté (https://github.com/ldematte) - MithunR (https://github.com/mythrocks) Approvers: - Chris Hegarty (https://github.com/ChrisHegarty) - MithunR (https://github.com/mythrocks) URL: #1067
1 parent 5500811 commit ecea00e

9 files changed

Lines changed: 113 additions & 152 deletions

File tree

java/cuvs-java/src/main/java/com/nvidia/cuvs/CagraIndex.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package com.nvidia.cuvs;
1717

18-
import com.nvidia.cuvs.BruteForceIndex.Builder;
1918
import com.nvidia.cuvs.spi.CuVSProvider;
2019
import java.io.InputStream;
2120
import java.io.OutputStream;

java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/BruteForceIndexImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ public SearchResults search(BruteForceQuery cuvsQuery) throws Throwable {
317317
checkCuVSError(returnValue, "cuvsRMMFree");
318318
}
319319

320-
return new BruteForceSearchResults(
320+
return BruteForceSearchResults.create(
321321
neighborsSequenceLayout,
322322
distancesSequenceLayout,
323323
neighborsMemorySegment,

java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/BruteForceSearchResults.java

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,56 +15,33 @@
1515
*/
1616
package com.nvidia.cuvs.internal;
1717

18-
import com.nvidia.cuvs.internal.common.SearchResultsImpl;
18+
import com.nvidia.cuvs.SearchResults;
1919
import java.lang.foreign.MemorySegment;
2020
import java.lang.foreign.SequenceLayout;
21-
import java.util.LinkedHashMap;
22-
import java.util.Map;
2321
import java.util.function.LongToIntFunction;
2422

2523
/**
2624
* SearchResult encapsulates the logic for reading and holding search results.
2725
*
2826
* @since 25.02
2927
*/
30-
public class BruteForceSearchResults extends SearchResultsImpl {
28+
class BruteForceSearchResults {
3129

32-
protected BruteForceSearchResults(
30+
static SearchResults create(
3331
SequenceLayout neighboursSequenceLayout,
3432
SequenceLayout distancesSequenceLayout,
3533
MemorySegment neighboursMemorySegment,
3634
MemorySegment distancesMemorySegment,
3735
int topK,
3836
LongToIntFunction mapping,
3937
long numberOfQueries) {
40-
super(
38+
return SearchResultsImpl.create(
4139
neighboursSequenceLayout,
4240
distancesSequenceLayout,
4341
neighboursMemorySegment,
4442
distancesMemorySegment,
4543
topK,
4644
mapping,
4745
numberOfQueries);
48-
readResultMemorySegments();
49-
}
50-
51-
/**
52-
* Reads neighbors and distances {@link MemorySegment} and loads the values
53-
* internally
54-
*/
55-
protected void readResultMemorySegments() {
56-
Map<Integer, Float> intermediateResultMap = new LinkedHashMap<Integer, Float>();
57-
int count = 0;
58-
for (long i = 0; i < topK * numberOfQueries; i++) {
59-
long id = (long) neighboursVarHandle.get(neighboursMemorySegment, 0L, i);
60-
float dst = (float) distancesVarHandle.get(distancesMemorySegment, 0L, i);
61-
intermediateResultMap.put(mapping.applyAsInt(id), dst);
62-
count += 1;
63-
if (count == topK) {
64-
results.add(intermediateResultMap);
65-
intermediateResultMap = new LinkedHashMap<Integer, Float>();
66-
count = 0;
67-
}
68-
}
6946
}
7047
}

java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/CagraIndexImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ public SearchResults search(CagraQuery query) throws Throwable {
360360
returnValue = cuvsRMMFree(cuvsRes, prefilterDP, C_INT_BYTE_SIZE * prefilterBytes);
361361
checkCuVSError(returnValue, "cuvsRMMFree");
362362

363-
return new CagraSearchResults(
363+
return CagraSearchResults.create(
364364
neighborsSequenceLayout,
365365
distancesSequenceLayout,
366366
neighborsMemorySegment,

java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/CagraSearchResults.java

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,13 @@
1515
*/
1616
package com.nvidia.cuvs.internal;
1717

18-
import com.nvidia.cuvs.internal.common.SearchResultsImpl;
18+
import com.nvidia.cuvs.SearchResults;
19+
import java.lang.foreign.MemoryLayout;
1920
import java.lang.foreign.MemorySegment;
2021
import java.lang.foreign.SequenceLayout;
2122
import java.util.LinkedHashMap;
23+
import java.util.LinkedList;
24+
import java.util.List;
2225
import java.util.Map;
2326
import java.util.function.LongToIntFunction;
2427

@@ -27,46 +30,44 @@
2730
*
2831
* @since 25.02
2932
*/
30-
public class CagraSearchResults extends SearchResultsImpl {
33+
class CagraSearchResults {
3134

32-
protected CagraSearchResults(
35+
/**
36+
* Factory method to create an on-heap SearchResults (backed by standard Java data types and containers) from
37+
* native/off-heap memory data structures.
38+
* This class provides its own implementation for reading from native memory instead of reling on
39+
* {@link SearchResultsImpl#create} because it requires special handling of neighbours IDs.
40+
*/
41+
static SearchResults create(
3342
SequenceLayout neighboursSequenceLayout,
3443
SequenceLayout distancesSequenceLayout,
3544
MemorySegment neighboursMemorySegment,
3645
MemorySegment distancesMemorySegment,
3746
int topK,
3847
LongToIntFunction mapping,
3948
long numberOfQueries) {
40-
super(
41-
neighboursSequenceLayout,
42-
distancesSequenceLayout,
43-
neighboursMemorySegment,
44-
distancesMemorySegment,
45-
topK,
46-
mapping,
47-
numberOfQueries);
48-
readResultMemorySegments();
49-
}
5049

51-
/**
52-
* Reads neighbors and distances {@link MemorySegment} and loads the values
53-
* internally
54-
*/
55-
protected void readResultMemorySegments() {
56-
Map<Integer, Float> intermediateResultMap = new LinkedHashMap<Integer, Float>();
50+
List<Map<Integer, Float>> results = new LinkedList<>();
51+
Map<Integer, Float> intermediateResultMap = new LinkedHashMap<>();
52+
var neighboursVarHandle =
53+
neighboursSequenceLayout.varHandle(MemoryLayout.PathElement.sequenceElement());
54+
var distancesVarHandle =
55+
distancesSequenceLayout.varHandle(MemoryLayout.PathElement.sequenceElement());
56+
5757
int count = 0;
5858
for (long i = 0; i < topK * numberOfQueries; i++) {
59-
long id = (long) neighboursVarHandle.get(neighboursMemorySegment, 0L, i);
59+
long id = (long) neighboursVarHandle.get(neighboursMemorySegment, 0, i);
6060
float dst = (float) distancesVarHandle.get(distancesMemorySegment, 0L, i);
6161
if (id != Integer.MAX_VALUE) {
6262
intermediateResultMap.put(mapping.applyAsInt(id), dst);
6363
}
6464
count += 1;
6565
if (count == topK) {
6666
results.add(intermediateResultMap);
67-
intermediateResultMap = new LinkedHashMap<Integer, Float>();
67+
intermediateResultMap = new LinkedHashMap<>();
6868
count = 0;
6969
}
7070
}
71+
return new SearchResultsImpl(results);
7172
}
7273
}

java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/HnswIndexImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public SearchResults search(HnswQuery query) throws Throwable {
133133
returnValue = cuvsStreamSync(cuvsRes);
134134
checkCuVSError(returnValue, "cuvsStreamSync");
135135

136-
return new HnswSearchResults(
136+
return HnswSearchResults.create(
137137
neighborsSequenceLayout,
138138
distancesSequenceLayout,
139139
neighborsMemorySegment,

java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/HnswSearchResults.java

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,56 +15,33 @@
1515
*/
1616
package com.nvidia.cuvs.internal;
1717

18-
import com.nvidia.cuvs.internal.common.SearchResultsImpl;
18+
import com.nvidia.cuvs.SearchResults;
1919
import java.lang.foreign.MemorySegment;
2020
import java.lang.foreign.SequenceLayout;
21-
import java.util.LinkedHashMap;
22-
import java.util.Map;
2321
import java.util.function.LongToIntFunction;
2422

2523
/**
2624
* SearchResult encapsulates the logic for reading and holding search results.
2725
*
2826
* @since 25.02
2927
*/
30-
public class HnswSearchResults extends SearchResultsImpl {
28+
class HnswSearchResults {
3129

32-
protected HnswSearchResults(
30+
static SearchResults create(
3331
SequenceLayout neighboursSequenceLayout,
3432
SequenceLayout distancesSequenceLayout,
3533
MemorySegment neighboursMemorySegment,
3634
MemorySegment distancesMemorySegment,
3735
int topK,
3836
LongToIntFunction mapping,
3937
long numberOfQueries) {
40-
super(
38+
return SearchResultsImpl.create(
4139
neighboursSequenceLayout,
4240
distancesSequenceLayout,
4341
neighboursMemorySegment,
4442
distancesMemorySegment,
4543
topK,
4644
mapping,
4745
numberOfQueries);
48-
readResultMemorySegments();
49-
}
50-
51-
/**
52-
* Reads neighbors and distances {@link MemorySegment} and loads the values
53-
* internally
54-
*/
55-
protected void readResultMemorySegments() {
56-
Map<Integer, Float> intermediateResultMap = new LinkedHashMap<Integer, Float>();
57-
int count = 0;
58-
for (long i = 0; i < topK * numberOfQueries; i++) {
59-
long id = (long) neighboursVarHandle.get(neighboursMemorySegment, 0L, i);
60-
float dst = (float) distancesVarHandle.get(distancesMemorySegment, 0L, i);
61-
intermediateResultMap.put(mapping.applyAsInt(id), dst);
62-
count += 1;
63-
if (count == topK) {
64-
results.add(intermediateResultMap);
65-
intermediateResultMap = new LinkedHashMap<Integer, Float>();
66-
count = 0;
67-
}
68-
}
6946
}
7047
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright (c) 2025, NVIDIA CORPORATION.
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 com.nvidia.cuvs.internal;
17+
18+
import com.nvidia.cuvs.SearchResults;
19+
import java.lang.foreign.MemoryLayout;
20+
import java.lang.foreign.MemorySegment;
21+
import java.lang.foreign.SequenceLayout;
22+
import java.util.LinkedHashMap;
23+
import java.util.LinkedList;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.function.LongToIntFunction;
27+
28+
class SearchResultsImpl implements SearchResults {
29+
30+
private final List<Map<Integer, Float>> results;
31+
32+
SearchResultsImpl(List<Map<Integer, Float>> results) {
33+
this.results = results;
34+
}
35+
36+
/**
37+
* Factory method to create an on-heap SearchResults (backed by standard Java data types and containers) from
38+
* native/off-heap memory data structures.
39+
*/
40+
static SearchResults create(
41+
SequenceLayout neighboursSequenceLayout,
42+
SequenceLayout distancesSequenceLayout,
43+
MemorySegment neighboursMemorySegment,
44+
MemorySegment distancesMemorySegment,
45+
int topK,
46+
LongToIntFunction mapping,
47+
long numberOfQueries) {
48+
List<Map<Integer, Float>> results = new LinkedList<>();
49+
Map<Integer, Float> intermediateResultMap = new LinkedHashMap<>();
50+
var neighboursVarHandle =
51+
neighboursSequenceLayout.varHandle(MemoryLayout.PathElement.sequenceElement());
52+
var distancesVarHandle =
53+
distancesSequenceLayout.varHandle(MemoryLayout.PathElement.sequenceElement());
54+
55+
int count = 0;
56+
for (long i = 0; i < topK * numberOfQueries; i++) {
57+
long id = (long) neighboursVarHandle.get(neighboursMemorySegment, 0L, i);
58+
float dst = (float) distancesVarHandle.get(distancesMemorySegment, 0L, i);
59+
intermediateResultMap.put(mapping != null ? mapping.applyAsInt((int) id) : (int) id, dst);
60+
count += 1;
61+
if (count == topK) {
62+
results.add(intermediateResultMap);
63+
intermediateResultMap = new LinkedHashMap<>();
64+
count = 0;
65+
}
66+
}
67+
68+
return new SearchResultsImpl(results);
69+
}
70+
71+
/**
72+
* Gets a list results as a map of neighbor IDs to distances.
73+
*
74+
* @return a list of results for each query as a map of neighbor IDs to distance
75+
*/
76+
@Override
77+
public List<Map<Integer, Float>> getResults() {
78+
return results;
79+
}
80+
}

java/cuvs-java/src/main/java22/com/nvidia/cuvs/internal/common/SearchResultsImpl.java

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)