Skip to content

Commit 015f370

Browse files
committed
Add fixed-cardinality SIMD for sorted numeric ranges
Dense fixed-cardinality sorted numeric values can evaluate range blocks with the vectorization provider when the flattened value layout is raw and contiguous. Keep the optimization gated to layouts that benchmark well and retain scalar fallback behavior for other encodings.
1 parent 610484e commit 015f370

4 files changed

Lines changed: 86 additions & 25 deletions

File tree

lucene/CHANGES.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ Improvements
291291

292292
Optimizations
293293
---------------------
294-
(No changes)
294+
* GITHUB#16283: Use Panama Vector API to SIMD-evaluate fixed-cardinality sorted numeric range
295+
queries in rangeIntoBitSet. (Costin Leau)
295296

296297
Bug Fixes
297298
---------------------

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
@@ -495,29 +495,6 @@ private static int fixedCardinality(
495495
return (int) cardinality;
496496
}
497497

498-
private static void sortedNumericScalarRangeIntoBitSet(
499-
LongValues values,
500-
int fromDoc,
501-
int toDoc,
502-
int cardinality,
503-
long minValue,
504-
long maxValue,
505-
FixedBitSet bitSet,
506-
int offset) {
507-
for (int doc = fromDoc; doc < toDoc; doc++) {
508-
long valueOffset = (long) doc * cardinality;
509-
for (int i = 0; i < cardinality; i++) {
510-
long value = values.get(valueOffset + i);
511-
if (value >= minValue) {
512-
if (value <= maxValue) {
513-
bitSet.set(doc - offset);
514-
}
515-
break;
516-
}
517-
}
518-
}
519-
}
520-
521498
private static boolean sortedNumericMatchesRange(
522499
LongValues values, long start, long end, long minValue, long maxValue) {
523500
for (long valueOffset = start; valueOffset < end; valueOffset++) {
@@ -1906,7 +1883,7 @@ public void rangeIntoBitSet(
19061883
}
19071884
int cardinality = denseFixedCardinality;
19081885
if (cardinality > 1) {
1909-
sortedNumericScalarRangeIntoBitSet(
1886+
DOC_VALUES_RANGE_SUPPORT.sortedNumericRangeIntoBitSet(
19101887
values, fromDoc, endDoc, cardinality, minValue, maxValue, bitSet, offset);
19111888
return;
19121889
}

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

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,31 @@ void rangeIntoBitSet(
5050
long maxValue,
5151
FixedBitSet bitSet,
5252
int offset);
53+
54+
/**
55+
* Fills {@code bitSet} with docs in {@code [fromDoc, toDoc)} whose {@code cardinality} sorted
56+
* values contain at least one value in {@code [minValue, maxValue]}.
57+
*/
58+
default void sortedNumericRangeIntoBitSet(
59+
LongValues values,
60+
int fromDoc,
61+
int toDoc,
62+
int cardinality,
63+
long minValue,
64+
long maxValue,
65+
FixedBitSet bitSet,
66+
int offset) {
67+
for (int doc = fromDoc; doc < toDoc; doc++) {
68+
long valueOffset = (long) doc * cardinality;
69+
for (int i = 0; i < cardinality; i++) {
70+
long value = values.get(valueOffset + i);
71+
if (value >= minValue) {
72+
if (value <= maxValue) {
73+
bitSet.set(doc - offset);
74+
}
75+
break;
76+
}
77+
}
78+
}
79+
}
5380
}

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

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

0 commit comments

Comments
 (0)