Skip to content

Commit f5a83a8

Browse files
committed
add bulk intoBitSet for SortedDocValues ordinal ranges
1 parent 3afffee commit f5a83a8

7 files changed

Lines changed: 1207 additions & 9 deletions

File tree

lucene/CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,8 @@ Optimizations
345345
* GITHUB#16370: Sort two-phase iterators by matchCost in DenseConjunctionBulkScorer.
346346
(Alan Woodward)
347347

348+
* GITHUB#16180: Add bulk intoBitSet for SortedDocValues ordinal ranges. (Prithvi S)
349+
348350
Bug Fixes
349351
---------------------
350352
* GITHUB#16350: Disable bulk-scoring in monitor queries. (Alan Woodward)

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,6 +1478,15 @@ public void intoBitSet(int upTo, FixedBitSet bitSet, int offset) throws IOExcept
14781478
advance(upTo);
14791479
}
14801480
}
1481+
1482+
@Override
1483+
public void ordinalRangeIntoBitSet(
1484+
int fromDoc, int toDoc, long minOrd, long maxOrd, FixedBitSet bitSet, int offset)
1485+
throws IOException {
1486+
// Dense packed ordinals: bulk evaluate via the same SIMD path as NumericDocValues
1487+
Lucene90DocValuesProducer.rangeIntoBitSet(
1488+
values, fromDoc, toDoc, minOrd, maxOrd, bitSet, offset);
1489+
}
14811490
};
14821491
} else if (ordsEntry.docsWithFieldOffset >= 0) { // sparse but non-empty
14831492
final IndexedDISI disi =
@@ -1576,6 +1585,15 @@ public int docIDRunEnd() throws IOException {
15761585
public void intoBitSet(int upTo, FixedBitSet bitSet, int offset) throws IOException {
15771586
ords.intoBitSet(upTo, bitSet, offset);
15781587
}
1588+
1589+
@Override
1590+
public void ordinalRangeIntoBitSet(
1591+
int fromDoc, int toDoc, long minOrd, long maxOrd, FixedBitSet bitSet, int offset)
1592+
throws IOException {
1593+
// Delegate to the underlying NumericDocValues which already has a (possibly SIMD)
1594+
// rangeIntoBitSet override.
1595+
ords.rangeIntoBitSet(fromDoc, toDoc, minOrd, maxOrd, bitSet, offset);
1596+
}
15791597
};
15801598
}
15811599

lucene/core/src/java/org/apache/lucene/index/SortedDocValues.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import org.apache.lucene.util.BytesRef;
21+
import org.apache.lucene.util.FixedBitSet;
2122
import org.apache.lucene.util.automaton.CompiledAutomaton;
2223

2324
/**
@@ -95,6 +96,34 @@ public TermsEnum termsEnum() throws IOException {
9596
return new SortedDocValuesTermsEnum(this);
9697
}
9798

99+
/**
100+
* Fills {@code bitSet} with the doc IDs in {@code [fromDoc, toDoc)} whose ordinals are in {@code
101+
* [minOrd, maxOrd]}. This is a bulk operation that avoids per-doc virtual dispatch overhead.
102+
*
103+
* <p>The default implementation falls back to per-doc evaluation via {@link #advanceExact} and
104+
* {@link #ordValue}. Subclasses with random-access storage (e.g., dense fixed-bitsPerValue
105+
* fields) can override this for significantly better performance.
106+
*
107+
* @param fromDoc first doc ID to evaluate (inclusive)
108+
* @param toDoc last doc ID to evaluate (exclusive)
109+
* @param minOrd lower bound of the ordinal range (inclusive)
110+
* @param maxOrd upper bound of the ordinal range (inclusive)
111+
* @param bitSet the bitset to fill
112+
* @param offset subtracted from each doc ID before setting the bit
113+
*/
114+
public void ordinalRangeIntoBitSet(
115+
int fromDoc, int toDoc, long minOrd, long maxOrd, FixedBitSet bitSet, int offset)
116+
throws IOException {
117+
for (int d = fromDoc; d < toDoc; d++) {
118+
if (advanceExact(d)) {
119+
int ord = ordValue();
120+
if (ord >= minOrd && ord <= maxOrd) {
121+
bitSet.set(d - offset);
122+
}
123+
}
124+
}
125+
}
126+
98127
/**
99128
* Returns a {@link TermsEnum} over the values, filtered by a {@link CompiledAutomaton} The enum
100129
* supports {@link TermsEnum#ord()}.

lucene/core/src/java/org/apache/lucene/search/DocValuesRangeIterator.java

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ public static DocValuesRangeIterator forOrdinalRange(
9999
};
100100
return skipper == null
101101
? new DocValuesValueRangeIterator(values, check, 2)
102-
: new BulkOrdinalRangeIterator(
103-
values, new SkipBlockRangeIterator(skipper, min, max), check, 2);
102+
: new BulkSortedRangeIterator(
103+
values, new SkipBlockRangeIterator(skipper, min, max), check, 2, min, max);
104104
}
105105

106106
/**
@@ -474,9 +474,43 @@ void intoMaybeBlock(int blockStart, int blockEnd, FixedBitSet bitSet, int offset
474474
}
475475

476476
/**
477-
* Bulk range iterator over ordinal (sorted / sorted-set) doc values. There is no columnar
478-
* range-decode like the numeric path, so MAYBE blocks confirm the ordinal predicate one doc at a
479-
* time, visiting only docs that have a value.
477+
* Bulk range iterator over single-valued sorted (ordinal) doc values. Delegates MAYBE blocks to
478+
* {@link SortedDocValues#ordinalRangeIntoBitSet} so that dense packed implementations can use
479+
* direct (SIMD) range evaluation over the ordinal array.
480+
*/
481+
private static final class BulkSortedRangeIterator extends BulkBlockRangeIterator {
482+
483+
private final SortedDocValues sortedValues;
484+
private final long minOrd;
485+
private final long maxOrd;
486+
487+
private BulkSortedRangeIterator(
488+
SortedDocValues values,
489+
SkipBlockRangeIterator blockIterator,
490+
IOBooleanSupplier predicate,
491+
float matchCost,
492+
long minOrd,
493+
long maxOrd) {
494+
super(values, blockIterator, predicate, matchCost);
495+
this.sortedValues = values;
496+
this.minOrd = minOrd;
497+
this.maxOrd = maxOrd;
498+
}
499+
500+
@Override
501+
void intoMaybeBlock(int blockStart, int blockEnd, FixedBitSet bitSet, int offset)
502+
throws IOException {
503+
// sortedValues is the same instance as disi, so a preceding matches() call may have
504+
// moved it beyond blockStart. Adjust the starting point.
505+
int from = Math.max(blockStart, sortedValues.docID());
506+
sortedValues.ordinalRangeIntoBitSet(from, blockEnd, minOrd, maxOrd, bitSet, offset);
507+
}
508+
}
509+
510+
/**
511+
* Bulk range iterator over ordinal (sorted-set) doc values. There is no columnar range-decode
512+
* like the numeric path, and for multi-valued we must check the predicate (which walks the
513+
* per-doc ords), so MAYBE blocks confirm by visiting only docs that have a value.
480514
*/
481515
private static final class BulkOrdinalRangeIterator extends BulkBlockRangeIterator {
482516

lucene/core/src/test/org/apache/lucene/search/TestDocValuesOrdinalRangeIterator.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,12 @@ public int getValueCount() {
134134

135135
@Override
136136
public boolean advanceExact(int target) {
137-
throw new UnsupportedOperationException();
137+
if (docHasValue(target)) {
138+
doc = target;
139+
return true;
140+
}
141+
doc = target;
142+
return false;
138143
}
139144

140145
@Override
@@ -349,7 +354,12 @@ public long getValueCount() {
349354

350355
@Override
351356
public boolean advanceExact(int target) {
352-
throw new UnsupportedOperationException();
357+
if (docHasValue(target)) {
358+
doc = target;
359+
return true;
360+
}
361+
doc = target;
362+
return false;
353363
}
354364

355365
@Override

lucene/core/src/test/org/apache/lucene/search/TestDocValuesOrdinalSetIterator.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,12 @@ public int getValueCount() {
209209

210210
@Override
211211
public boolean advanceExact(int target) {
212-
throw new UnsupportedOperationException();
212+
if (docHasValue(target)) {
213+
doc = target;
214+
return true;
215+
}
216+
doc = target;
217+
return false;
213218
}
214219

215220
@Override
@@ -500,7 +505,12 @@ public long getValueCount() {
500505

501506
@Override
502507
public boolean advanceExact(int target) {
503-
throw new UnsupportedOperationException();
508+
if (docHasValue(target)) {
509+
doc = target;
510+
return true;
511+
}
512+
doc = target;
513+
return false;
504514
}
505515

506516
@Override

0 commit comments

Comments
 (0)