Skip to content

Commit 44b58be

Browse files
committed
Improve hnsw on heap ram est (#14765)
* Disable HNSW connectedComponents (#14214) * Adjusting OnHeapHnswGraph RAM estimation to be incremental during build * changes * addressing pr comments * removing extra logging
1 parent 0c75f3f commit 44b58be

File tree

4 files changed

+55
-61
lines changed

4 files changed

+55
-61
lines changed

lucene/CHANGES.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ Optimizations
5151

5252
* GITHUB#14447: Compute the doc range more efficiently when flushing doc block. (Pan Guixin)
5353

54-
* GITHUB#14527: Reduce NeighborArray heap memory. (weizijun)
54+
* GITHUB#14527, GITHUB#14765: Reduce NeighborArray heap memory. (weizijun, Ben Trent)
5555

5656
* GITHUB#14529, GITHUB#14555, GITHUB#14618: Impl intoBitset for IndexedDISI and Docvalues. (Guo Feng)
5757

lucene/core/src/java/org/apache/lucene/util/hnsw/HnswGraphBuilder.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -452,7 +452,6 @@ void finish() throws IOException {
452452
// see: https://github.com/apache/lucene/issues/14214
453453
// connectComponents();
454454
frozen = true;
455-
hnsw.finishBuild();
456455
}
457456

458457
@SuppressWarnings("unused")

lucene/core/src/java/org/apache/lucene/util/hnsw/NeighborArray.java

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919

2020
import java.io.IOException;
2121
import java.util.Arrays;
22+
import java.util.function.LongConsumer;
2223
import org.apache.lucene.internal.hppc.MaxSizedFloatArrayList;
2324
import org.apache.lucene.internal.hppc.MaxSizedIntArrayList;
24-
import org.apache.lucene.util.Accountable;
2525
import org.apache.lucene.util.RamUsageEstimator;
2626

2727
/**
@@ -32,7 +32,7 @@
3232
*
3333
* @lucene.internal
3434
*/
35-
public class NeighborArray implements Accountable {
35+
public class NeighborArray {
3636
private static final long BASE_RAM_BYTES_USED =
3737
RamUsageEstimator.shallowSizeOfInstance(NeighborArray.class);
3838

@@ -42,12 +42,23 @@ public class NeighborArray implements Accountable {
4242
private final MaxSizedFloatArrayList scores;
4343
private final MaxSizedIntArrayList nodes;
4444
private int sortedNodeSize;
45+
private long ramBytesUsed = BASE_RAM_BYTES_USED;
46+
private final LongConsumer onHeapMemoryUsageListener;
4547

4648
public NeighborArray(int maxSize, boolean descOrder) {
49+
this(maxSize, descOrder, null);
50+
}
51+
52+
public NeighborArray(int maxSize, boolean descOrder, LongConsumer onHeapMemoryUsageListener) {
4753
this.maxSize = maxSize;
4854
nodes = new MaxSizedIntArrayList(maxSize, maxSize / 8);
4955
scores = new MaxSizedFloatArrayList(maxSize, maxSize / 8);
56+
this.ramBytesUsed += nodes.ramBytesUsed() + scores.ramBytesUsed();
5057
this.scoresDescOrder = descOrder;
58+
this.onHeapMemoryUsageListener = onHeapMemoryUsageListener;
59+
if (onHeapMemoryUsageListener != null) {
60+
onHeapMemoryUsageListener.accept(ramBytesUsed);
61+
}
5162
}
5263

5364
/**
@@ -68,8 +79,10 @@ public void addInOrder(int newNode, float newScore) {
6879
+ " to "
6980
+ Arrays.toString(scores.toArray());
7081
}
82+
int previousLength = nodes.buffer.length;
7183
nodes.add(newNode);
7284
scores.add(newScore);
85+
alertOnHeapMemoryUsageChange(nodes.buffer.length, previousLength);
7386
++size;
7487
++sortedNodeSize;
7588
}
@@ -79,12 +92,21 @@ public void addOutOfOrder(int newNode, float newScore) {
7992
if (size == maxSize) {
8093
throw new IllegalStateException("No growth is allowed");
8194
}
82-
95+
int previousLength = nodes.buffer.length;
8396
nodes.add(newNode);
8497
scores.add(newScore);
98+
alertOnHeapMemoryUsageChange(nodes.buffer.length, previousLength);
8599
size++;
86100
}
87101

102+
private void alertOnHeapMemoryUsageChange(int newLength, int previousLength) {
103+
if (newLength > previousLength && onHeapMemoryUsageListener != null) {
104+
int lengthDelta = newLength - previousLength;
105+
onHeapMemoryUsageListener.accept(
106+
(long) (lengthDelta) * Integer.BYTES + (long) (lengthDelta) * Float.BYTES);
107+
}
108+
}
109+
88110
/**
89111
* In addition to {@link #addOutOfOrder(int, float)}, this function will also remove the
90112
* least-diverse node if the node array is full after insertion
@@ -313,9 +335,4 @@ private boolean isWorstNonDiverse(
313335
public int maxSize() {
314336
return maxSize;
315337
}
316-
317-
@Override
318-
public long ramBytesUsed() {
319-
return BASE_RAM_BYTES_USED + nodes.ramBytesUsed() + scores.ramBytesUsed();
320-
}
321338
}

lucene/core/src/java/org/apache/lucene/util/hnsw/OnHeapHnswGraph.java

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
*/
3333
public final class OnHeapHnswGraph extends HnswGraph implements Accountable {
3434

35+
// shallow estimate of the statically used on-heap memory.
36+
private static final long RAM_BYTES_USED =
37+
RamUsageEstimator.shallowSizeOfInstance(OnHeapHnswGraph.class);
38+
3539
private static final int INIT_SIZE = 128;
3640

3741
private final AtomicReference<EntryNode> entryNode;
@@ -83,6 +87,7 @@ public final class OnHeapHnswGraph extends HnswGraph implements Accountable {
8387
numNodes = INIT_SIZE;
8488
}
8589
this.graph = new NeighborArray[numNodes][];
90+
this.graphRamBytesUsed = RAM_BYTES_USED + RamUsageEstimator.shallowSizeOf(graph);
8691
}
8792

8893
/**
@@ -151,21 +156,28 @@ public void addNode(int level, int node) {
151156
size.incrementAndGet();
152157
}
153158
if (level == 0) {
154-
graph[node][level] = new NeighborArray(nsize0, true);
159+
graph[node][level] =
160+
new NeighborArray(
161+
nsize0,
162+
true,
163+
l -> {
164+
assert l > 0;
165+
long bytesUsed = graphRamBytesUsed;
166+
graphRamBytesUsed = bytesUsed + l;
167+
});
155168
} else {
156-
graph[node][level] = new NeighborArray(nsize, true);
169+
graph[node][level] =
170+
new NeighborArray(
171+
nsize,
172+
true,
173+
l -> {
174+
assert l > 0;
175+
long bytesUsed = graphRamBytesUsed;
176+
graphRamBytesUsed = bytesUsed + l;
177+
});
157178
nonZeroLevelSize.incrementAndGet();
158179
}
159180
maxNodeId.accumulateAndGet(node, Math::max);
160-
// update graphRamBytesUsed every 1000 nodes
161-
if (level == 0 && node % 1000 == 0) {
162-
updateGraphRamBytesUsed();
163-
}
164-
}
165-
166-
/** Finish building the graph. */
167-
public void finishBuild() {
168-
updateGraphRamBytesUsed();
169181
}
170182

171183
@Override
@@ -296,48 +308,14 @@ private void generateLevelToNodes() {
296308
lastFreezeSize = size();
297309
}
298310

299-
/** Update the estimated ram bytes used for the neighbor array. */
300-
public void updateGraphRamBytesUsed() {
301-
long currentRamBytesUsedEstimate = RamUsageEstimator.NUM_BYTES_ARRAY_HEADER;
302-
for (int node = 0; node < graph.length; node++) {
303-
if (graph[node] == null) {
304-
continue;
305-
}
306-
307-
for (int i = 0; i < graph[node].length; i++) {
308-
if (graph[node][i] == null) {
309-
continue;
310-
}
311-
currentRamBytesUsedEstimate += graph[node][i].ramBytesUsed();
312-
}
313-
314-
currentRamBytesUsedEstimate += RamUsageEstimator.NUM_BYTES_OBJECT_HEADER;
315-
}
316-
graphRamBytesUsed = currentRamBytesUsedEstimate;
317-
}
318-
311+
/**
312+
* Provides an estimate of the current on-heap memory usage of the graph. This is not threadsafe,
313+
* meaning the heap utilization if building the graph concurrently may be inaccurate. The main
314+
* purpose of this method is during initial document indexing and flush.
315+
*/
319316
@Override
320317
public long ramBytesUsed() {
321-
long total = graphRamBytesUsed; // all NeighborArray
322-
total += 4 * Integer.BYTES; // all int fields
323-
total += 1; // field: noGrowth
324-
total +=
325-
RamUsageEstimator.NUM_BYTES_OBJECT_REF
326-
+ RamUsageEstimator.NUM_BYTES_OBJECT_HEADER
327-
+ 2 * Integer.BYTES; // field: entryNode
328-
total += 3L * (Integer.BYTES + RamUsageEstimator.NUM_BYTES_OBJECT_HEADER); // 3 AtomicInteger
329-
total += RamUsageEstimator.NUM_BYTES_OBJECT_REF; // field: cur
330-
total += RamUsageEstimator.NUM_BYTES_ARRAY_HEADER; // field: levelToNodes
331-
if (levelToNodes != null) {
332-
total +=
333-
(long) (numLevels() - 1) * RamUsageEstimator.NUM_BYTES_OBJECT_REF; // no cost for level 0
334-
total +=
335-
(long) nonZeroLevelSize.get()
336-
* (RamUsageEstimator.NUM_BYTES_OBJECT_HEADER
337-
+ RamUsageEstimator.NUM_BYTES_OBJECT_HEADER
338-
+ Integer.BYTES);
339-
}
340-
return total;
318+
return graphRamBytesUsed;
341319
}
342320

343321
@Override

0 commit comments

Comments
 (0)