Skip to content

Commit d50a719

Browse files
authored
Merge branch 'main' into lucene/sorted-numeric-gcd-range
2 parents 6c1f666 + 42e71d8 commit d50a719

5 files changed

Lines changed: 105 additions & 25 deletions

File tree

lucene/CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ Optimizations
320320

321321
* GITHUB#16160: Improve numeric doc values range query performance for dense fields that use GCD or delta encoding. (Costin Leau)
322322

323+
* GITHUB#16283: Use Panama Vector API to SIMD-evaluate fixed-cardinality sorted numeric range queries in
324+
rangeIntoBitSet. (Costin Leau)
325+
323326
* GITHUB#16285: Apply GCD bound transform to sorted numeric rangeIntoBitSet, comparing raw
324327
encoded values directly instead of decoding every packed value. (Costin Leau)
325328

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

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -569,29 +569,6 @@ private static int fixedCardinality(
569569
return (int) cardinality;
570570
}
571571

572-
private static void sortedNumericScalarRangeIntoBitSet(
573-
LongValues values,
574-
int fromDoc,
575-
int toDoc,
576-
int cardinality,
577-
long minValue,
578-
long maxValue,
579-
FixedBitSet bitSet,
580-
int offset) {
581-
for (int doc = fromDoc; doc < toDoc; doc++) {
582-
long valueOffset = (long) doc * cardinality;
583-
for (int i = 0; i < cardinality; i++) {
584-
long value = values.get(valueOffset + i);
585-
if (value >= minValue) {
586-
if (value <= maxValue) {
587-
bitSet.set(doc - offset);
588-
}
589-
break;
590-
}
591-
}
592-
}
593-
}
594-
595572
private static boolean sortedNumericMatchesRange(
596573
LongValues values, long start, long end, long minValue, long maxValue) {
597574
for (long valueOffset = start; valueOffset < end; valueOffset++) {
@@ -2073,7 +2050,7 @@ public void rangeIntoBitSet(
20732050
}
20742051
int cardinality = denseFixedCardinality;
20752052
if (cardinality > 1) {
2076-
sortedNumericScalarRangeIntoBitSet(
2053+
DOC_VALUES_RANGE_SUPPORT.sortedNumericRangeIntoBitSet(
20772054
values, fromDoc, endDoc, cardinality, minValue, maxValue, bitSet, offset);
20782055
return;
20792056
}

lucene/core/src/java/org/apache/lucene/internal/vectorization/DocValuesRangeSupport.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,36 @@ void rangeIntoBitSet(
5050
long maxValue,
5151
FixedBitSet bitSet,
5252
int offset);
53+
54+
/**
55+
* Fills {@code bitSet} with docs in {@code [fromDoc, toDoc)} whose sorted numeric values contain
56+
* at least one value in {@code [minValue, maxValue]}.
57+
*
58+
* <p>This method only supports <b>fixed-cardinality</b> fields where every document has exactly
59+
* {@code cardinality} values stored contiguously starting at index {@code doc * cardinality}.
60+
*
61+
* @param cardinality number of values per document (must be &gt; 0)
62+
*/
63+
default void sortedNumericRangeIntoBitSet(
64+
LongValues values,
65+
int fromDoc,
66+
int toDoc,
67+
int cardinality,
68+
long minValue,
69+
long maxValue,
70+
FixedBitSet bitSet,
71+
int offset) {
72+
for (int doc = fromDoc; doc < toDoc; doc++) {
73+
long valueOffset = (long) doc * cardinality;
74+
for (int i = 0; i < cardinality; i++) {
75+
long value = values.get(valueOffset + i);
76+
if (value >= minValue) {
77+
if (value <= maxValue) {
78+
bitSet.set(doc - offset);
79+
}
80+
break;
81+
}
82+
}
83+
}
84+
}
5385
}

lucene/core/src/java25/org/apache/lucene/internal/vectorization/PanamaDocValuesRangeSupport.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,61 @@ public void rangeIntoBitSet(
6969
}
7070
}
7171
}
72+
73+
@Override
74+
public void sortedNumericRangeIntoBitSet(
75+
LongValues values,
76+
int fromDoc,
77+
int toDoc,
78+
int cardinality,
79+
long minValue,
80+
long maxValue,
81+
FixedBitSet bitSet,
82+
int offset) {
83+
assert cardinality > 0 : "cardinality must be positive: " + cardinality;
84+
final int vectorLen = LONG_SPECIES.length();
85+
final int docsPerVector = vectorLen / cardinality;
86+
if (docsPerVector == 0 || vectorLen % cardinality != 0) {
87+
DocValuesRangeSupport.super.sortedNumericRangeIntoBitSet(
88+
values, fromDoc, toDoc, cardinality, minValue, maxValue, bitSet, offset);
89+
return;
90+
}
91+
92+
final long[] scratch = new long[vectorLen];
93+
final int vectorDocEnd = fromDoc + (toDoc - fromDoc) / docsPerVector * docsPerVector;
94+
int doc = fromDoc;
95+
for (; doc < vectorDocEnd; doc += docsPerVector) {
96+
long valueOffset = (long) doc * cardinality;
97+
for (int lane = 0; lane < vectorLen; lane++) {
98+
scratch[lane] = values.get(valueOffset + lane);
99+
}
100+
LongVector vector = LongVector.fromArray(LONG_SPECIES, scratch, 0);
101+
// Flat range check on all lanes. Equivalent to the scalar early-break on sorted values:
102+
// a value outside [min,max] that is >= min must be > max, so its lane stays unset, and
103+
// collapseToDocMask OR-reduces lanes per doc to match "any value in range" semantics.
104+
VectorMask<Long> inRange =
105+
vector
106+
.compare(VectorOperators.GE, minValue)
107+
.and(vector.compare(VectorOperators.LE, maxValue));
108+
long docMask = collapseToDocMask(inRange.toLong(), cardinality, docsPerVector);
109+
if (docMask != 0) {
110+
bitSet.orMask(doc - offset, docMask, docsPerVector);
111+
}
112+
}
113+
114+
DocValuesRangeSupport.super.sortedNumericRangeIntoBitSet(
115+
values, doc, toDoc, cardinality, minValue, maxValue, bitSet, offset);
116+
}
117+
118+
private static long collapseToDocMask(long valueMask, int cardinality, int docsPerVector) {
119+
final long valueMaskPerDoc = (1L << cardinality) - 1L;
120+
long docMask = 0L;
121+
for (int d = 0; d < docsPerVector; d++) {
122+
long perDoc = (valueMask >>> (d * cardinality)) & valueMaskPerDoc;
123+
if (perDoc != 0) {
124+
docMask |= 1L << d;
125+
}
126+
}
127+
return docMask;
128+
}
72129
}

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

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -764,14 +764,25 @@ public void testSortedNumericRangeIntoBitSetSparseVariableCardinality() throws E
764764

765765
private void doTestSortedNumericRangeIntoBitSet(boolean dense, boolean fixedCardinality)
766766
throws Exception {
767+
doTestSortedNumericRangeIntoBitSet(dense, fixedCardinality, 4);
768+
}
769+
770+
public void testSortedNumericRangeIntoBitSetVaryingCardinality() throws Exception {
771+
for (int cardinality : new int[] {2, 3, 4, 5, 7, 8}) {
772+
doTestSortedNumericRangeIntoBitSet(true, true, cardinality);
773+
}
774+
}
775+
776+
private void doTestSortedNumericRangeIntoBitSet(
777+
boolean dense, boolean fixedCardinality, int fixedCardinalityValue) throws Exception {
767778
int numDocs = 4096 * 2;
768779
try (Directory dir = newDirectory()) {
769780
IndexWriterConfig iwc = new IndexWriterConfig().setCodec(new Lucene104Codec());
770781
try (IndexWriter w = new IndexWriter(dir, iwc)) {
771782
for (int docID = 0; docID < numDocs; docID++) {
772783
Document doc = new Document();
773784
if (dense || docID % 3 != 0) {
774-
int valueCount = fixedCardinality ? 4 : 1 + (docID & 3);
785+
int valueCount = fixedCardinality ? fixedCardinalityValue : 1 + (docID & 3);
775786
long firstValue = (docID * 13L) % 100;
776787
for (int i = 0; i < valueCount; i++) {
777788
doc.add(SortedNumericDocValuesField.indexedField("sn", firstValue + i * 3L));

0 commit comments

Comments
 (0)