Skip to content

Commit b443015

Browse files
committed
LUCENE-10384: Simplify LongHeap. (#615)
The min/max ordering logic moves to NeighborQueue.
1 parent 225fd15 commit b443015

File tree

4 files changed

+61
-112
lines changed

4 files changed

+61
-112
lines changed

lucene/core/src/java/org/apache/lucene/codecs/lucene90/PForUtil.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ static boolean allEqual(long[] l) {
6060
/** Encode 128 integers from {@code longs} into {@code out}. */
6161
void encode(long[] longs, DataOutput out) throws IOException {
6262
// Determine the top MAX_EXCEPTIONS + 1 values
63-
final LongHeap top = LongHeap.create(LongHeap.Order.MIN, MAX_EXCEPTIONS + 1);
63+
final LongHeap top = new LongHeap(MAX_EXCEPTIONS + 1);
6464
for (int i = 0; i <= MAX_EXCEPTIONS; ++i) {
6565
top.push(longs[i]);
6666
}

lucene/core/src/java/org/apache/lucene/util/LongHeap.java

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -17,27 +17,16 @@
1717
package org.apache.lucene.util;
1818

1919
/**
20-
* A heap that stores longs; a primitive priority queue that like all priority queues maintains a
21-
* partial ordering of its elements such that the least element can always be found in constant
20+
* A min heap that stores longs; a primitive priority queue that like all priority queues maintains
21+
* a partial ordering of its elements such that the least element can always be found in constant
2222
* time. Put()'s and pop()'s require log(size). This heap provides unbounded growth via {@link
2323
* #push(long)}, and bounded-size insertion based on its nominal maxSize via {@link
24-
* #insertWithOverflow(long)}. The heap may be either a min heap, in which case the least element is
25-
* the smallest integer, or a max heap, when it is the largest, depending on the Order parameter.
24+
* #insertWithOverflow(long)}. The heap is a min heap, meaning that the top element is the lowest
25+
* value of the heap.
2626
*
2727
* @lucene.internal
2828
*/
29-
public abstract class LongHeap {
30-
31-
/**
32-
* Used to specify the ordering of the heap. A min-heap provides access to the minimum element in
33-
* constant time, and when bounded, retains the maximum <code>maxSize</code> elements. A max-heap
34-
* conversely provides access to the maximum element in constant time, and when bounded retains
35-
* the minimum <code>maxSize</code> elements.
36-
*/
37-
public enum Order {
38-
MIN,
39-
MAX
40-
}
29+
public final class LongHeap {
4130

4231
private final int maxSize;
4332

@@ -50,7 +39,7 @@ public enum Order {
5039
* @param maxSize the maximum size of the heap, or if negative, the initial size of an unbounded
5140
* heap
5241
*/
53-
LongHeap(int maxSize) {
42+
public LongHeap(int maxSize) {
5443
final int heapSize;
5544
if (maxSize < 1 || maxSize >= ArrayUtil.MAX_ARRAY_LENGTH) {
5645
// Throw exception to prevent confusing OOME:
@@ -63,33 +52,6 @@ public enum Order {
6352
this.heap = new long[heapSize];
6453
}
6554

66-
public static LongHeap create(Order order, int maxSize) {
67-
// TODO: override push() for unbounded queue
68-
if (order == Order.MIN) {
69-
return new LongHeap(maxSize) {
70-
@Override
71-
public boolean lessThan(long a, long b) {
72-
return a < b;
73-
}
74-
};
75-
} else {
76-
return new LongHeap(maxSize) {
77-
@Override
78-
public boolean lessThan(long a, long b) {
79-
return a > b;
80-
}
81-
};
82-
}
83-
}
84-
85-
/**
86-
* Determines the ordering of objects in this priority queue. Subclasses must define this one
87-
* method.
88-
*
89-
* @return <code>true</code> iff parameter <code>a</code> is less than parameter <code>b</code>.
90-
*/
91-
public abstract boolean lessThan(long a, long b);
92-
9355
/**
9456
* Adds a value in log(size) time. Grows unbounded as needed to accommodate new values.
9557
*
@@ -114,7 +76,7 @@ public final long push(long element) {
11476
*/
11577
public boolean insertWithOverflow(long value) {
11678
if (size >= maxSize) {
117-
if (lessThan(value, heap[1])) {
79+
if (value < heap[1]) {
11880
return false;
11981
}
12082
updateTop(value);
@@ -190,7 +152,7 @@ private void upHeap(int origPos) {
190152
int i = origPos;
191153
long value = heap[i]; // save bottom value
192154
int j = i >>> 1;
193-
while (j > 0 && lessThan(value, heap[j])) {
155+
while (j > 0 && value < heap[j]) {
194156
heap[i] = heap[j]; // shift parents down
195157
i = j;
196158
j = j >>> 1;
@@ -202,15 +164,15 @@ private void downHeap(int i) {
202164
long value = heap[i]; // save top value
203165
int j = i << 1; // find smaller child
204166
int k = j + 1;
205-
if (k <= size && lessThan(heap[k], heap[j])) {
167+
if (k <= size && heap[k] < heap[j]) {
206168
j = k;
207169
}
208-
while (j <= size && lessThan(heap[j], value)) {
170+
while (j <= size && heap[j] < value) {
209171
heap[i] = heap[j]; // shift up child
210172
i = j;
211173
j = i << 1;
212174
k = j + 1;
213-
if (k <= size && lessThan(heap[k], heap[j])) {
175+
if (k <= size && heap[k] < heap[j]) {
214176
j = k;
215177
}
216178
}
@@ -236,7 +198,8 @@ public long get(int i) {
236198
*
237199
* @lucene.internal
238200
*/
239-
protected final long[] getHeapArray() {
201+
// pkg-private for testing
202+
final long[] getHeapArray() {
240203
return heap;
241204
}
242205
}

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

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,34 @@
2929
*/
3030
public class NeighborQueue {
3131

32+
private static enum Order {
33+
NATURAL {
34+
@Override
35+
long apply(long v) {
36+
return v;
37+
}
38+
},
39+
REVERSED {
40+
@Override
41+
long apply(long v) {
42+
// This cannot be just `-v` since Long.MIN_VALUE doesn't have a positive counterpart. It
43+
// needs a function that returns MAX_VALUE for MIN_VALUE and vice-versa.
44+
return -1 - v;
45+
}
46+
};
47+
48+
abstract long apply(long v);
49+
}
50+
3251
private final LongHeap heap;
52+
private final Order order;
3353

3454
// Used to track the number of neighbors visited during a single graph traversal
3555
private int visitedCount;
3656

3757
NeighborQueue(int initialSize, boolean reversed) {
38-
if (reversed) {
39-
heap = LongHeap.create(LongHeap.Order.MAX, initialSize);
40-
} else {
41-
heap = LongHeap.create(LongHeap.Order.MIN, initialSize);
42-
}
58+
this.heap = new LongHeap(initialSize);
59+
this.order = reversed ? Order.REVERSED : Order.NATURAL;
4360
}
4461

4562
/** @return the number of elements in the heap */
@@ -71,12 +88,12 @@ public boolean insertWithOverflow(int newNode, float newScore) {
7188
}
7289

7390
private long encode(int node, float score) {
74-
return (((long) NumericUtils.floatToSortableInt(score)) << 32) | node;
91+
return order.apply((((long) NumericUtils.floatToSortableInt(score)) << 32) | node);
7592
}
7693

7794
/** Removes the top element and returns its node id. */
7895
public int pop() {
79-
return (int) heap.pop();
96+
return (int) order.apply(heap.pop());
8097
}
8198

8299
int[] nodes() {
@@ -90,12 +107,12 @@ int[] nodes() {
90107

91108
/** Returns the top element's node id. */
92109
public int topNode() {
93-
return (int) heap.top();
110+
return (int) order.apply(heap.top());
94111
}
95112

96113
/** Returns the top element's node score. */
97114
public float topScore() {
98-
return NumericUtils.sortableIntToFloat((int) (heap.top() >> 32));
115+
return NumericUtils.sortableIntToFloat((int) (order.apply(heap.top()) >> 32));
99116
}
100117

101118
public int visitedCount() {

lucene/core/src/test/org/apache/lucene/util/TestLongHeap.java

Lines changed: 21 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -16,36 +16,18 @@
1616
*/
1717
package org.apache.lucene.util;
1818

19-
import static org.apache.lucene.util.LongHeap.Order.MAX;
20-
import static org.apache.lucene.util.LongHeap.Order.MIN;
21-
2219
import java.util.ArrayList;
2320
import java.util.Random;
2421
import org.apache.lucene.tests.util.LuceneTestCase;
2522
import org.apache.lucene.tests.util.TestUtil;
2623

2724
public class TestLongHeap extends LuceneTestCase {
2825

29-
private static class AssertingLongHeap extends LongHeap {
30-
AssertingLongHeap(int count) {
31-
super(count);
32-
}
33-
34-
@Override
35-
public boolean lessThan(long a, long b) {
36-
return (a < b);
37-
}
38-
39-
final void checkValidity() {
40-
long[] heapArray = getHeapArray();
41-
for (int i = 1; i <= size(); i++) {
42-
int parent = i >>> 1;
43-
if (parent > 1) {
44-
if (lessThan(heapArray[parent], heapArray[i]) == false) {
45-
assertEquals(heapArray[parent], heapArray[i]);
46-
}
47-
}
48-
}
26+
private static void checkValidity(LongHeap heap) {
27+
long[] heapArray = heap.getHeapArray();
28+
for (int i = 2; i <= heap.size(); i++) {
29+
int parent = i >>> 1;
30+
assert heapArray[parent] <= heapArray[i];
4931
}
5032
}
5133

@@ -54,7 +36,7 @@ public void testPQ() {
5436
}
5537

5638
public static void testPQ(int count, Random gen) {
57-
LongHeap pq = LongHeap.create(MIN, count);
39+
LongHeap pq = new LongHeap(count);
5840
long sum = 0, sum2 = 0;
5941

6042
for (int i = 0; i < count; i++) {
@@ -75,7 +57,7 @@ public static void testPQ(int count, Random gen) {
7557
}
7658

7759
public void testClear() {
78-
LongHeap pq = LongHeap.create(MIN, 3);
60+
LongHeap pq = new LongHeap(3);
7961
pq.push(2);
8062
pq.push(3);
8163
pq.push(1);
@@ -85,7 +67,7 @@ public void testClear() {
8567
}
8668

8769
public void testExceedBounds() {
88-
LongHeap pq = LongHeap.create(MIN, 1);
70+
LongHeap pq = new LongHeap(1);
8971
pq.push(2);
9072
pq.push(0);
9173
// expectThrows(ArrayIndexOutOfBoundsException.class, () -> pq.push(0));
@@ -94,7 +76,7 @@ public void testExceedBounds() {
9476
}
9577

9678
public void testFixedSize() {
97-
LongHeap pq = LongHeap.create(MIN, 3);
79+
LongHeap pq = new LongHeap(3);
9880
pq.insertWithOverflow(2);
9981
pq.insertWithOverflow(3);
10082
pq.insertWithOverflow(1);
@@ -105,20 +87,8 @@ public void testFixedSize() {
10587
assertEquals(3, pq.top());
10688
}
10789

108-
public void testFixedSizeMax() {
109-
LongHeap pq = LongHeap.create(MAX, 3);
110-
pq.insertWithOverflow(2);
111-
pq.insertWithOverflow(3);
112-
pq.insertWithOverflow(1);
113-
pq.insertWithOverflow(5);
114-
pq.insertWithOverflow(7);
115-
pq.insertWithOverflow(1);
116-
assertEquals(3, pq.size());
117-
assertEquals(2, pq.top());
118-
}
119-
12090
public void testDuplicateValues() {
121-
LongHeap pq = LongHeap.create(MIN, 3);
91+
LongHeap pq = new LongHeap(3);
12292
pq.push(2);
12393
pq.push(3);
12494
pq.push(1);
@@ -131,7 +101,7 @@ public void testDuplicateValues() {
131101
public void testInsertions() {
132102
Random random = random();
133103
int numDocsInPQ = TestUtil.nextInt(random, 1, 100);
134-
AssertingLongHeap pq = new AssertingLongHeap(numDocsInPQ);
104+
LongHeap pq = new LongHeap(numDocsInPQ);
135105
Long lastLeast = null;
136106

137107
// Basic insertion of new content
@@ -140,7 +110,7 @@ public void testInsertions() {
140110
long newEntry = Math.abs(random.nextLong());
141111
sds.add(newEntry);
142112
pq.insertWithOverflow(newEntry);
143-
pq.checkValidity();
113+
checkValidity(pq);
144114
long newLeast = pq.top();
145115
if ((lastLeast != null) && (newLeast != newEntry) && (newLeast != lastLeast)) {
146116
// If there has been a change of least entry and it wasn't our new
@@ -153,17 +123,16 @@ public void testInsertions() {
153123
}
154124

155125
public void testInvalid() {
156-
expectThrows(IllegalArgumentException.class, () -> LongHeap.create(MAX, -1));
157-
expectThrows(IllegalArgumentException.class, () -> LongHeap.create(MAX, 0));
158-
expectThrows(
159-
IllegalArgumentException.class, () -> LongHeap.create(MAX, ArrayUtil.MAX_ARRAY_LENGTH));
126+
expectThrows(IllegalArgumentException.class, () -> new LongHeap(-1));
127+
expectThrows(IllegalArgumentException.class, () -> new LongHeap(0));
128+
expectThrows(IllegalArgumentException.class, () -> new LongHeap(ArrayUtil.MAX_ARRAY_LENGTH));
160129
}
161130

162131
public void testUnbounded() {
163132
int initialSize = random().nextInt(10) + 1;
164-
LongHeap pq = LongHeap.create(MAX, initialSize);
133+
LongHeap pq = new LongHeap(initialSize);
165134
int num = random().nextInt(100) + 1;
166-
long minValue = Long.MAX_VALUE;
135+
long maxValue = Long.MIN_VALUE;
167136
int count = 0;
168137
for (int i = 0; i < num; i++) {
169138
long value = random().nextLong();
@@ -178,19 +147,19 @@ public void testUnbounded() {
178147
}
179148
}
180149
}
181-
minValue = Math.min(minValue, value);
150+
maxValue = Math.max(maxValue, value);
182151
}
183152
assertEquals(count, pq.size());
184-
long last = Long.MAX_VALUE;
153+
long last = Long.MIN_VALUE;
185154
while (pq.size() > 0) {
186155
long top = pq.top();
187156
long next = pq.pop();
188157
assertEquals(top, next);
189158
--count;
190-
assertTrue(next <= last);
159+
assertTrue(next >= last);
191160
last = next;
192161
}
193162
assertEquals(0, count);
194-
assertEquals(minValue, last);
163+
assertEquals(maxValue, last);
195164
}
196165
}

0 commit comments

Comments
 (0)