You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by jp...@apache.org on 2025/03/13 13:16:51 UTC
(lucene) branch branch_10x updated: Speed up scoring conjunctions a bit. (#14345)
This is an automated email from the ASF dual-hosted git repository.
jpountz pushed a commit to branch branch_10x
in repository https://gitbox.apache.org/repos/asf/lucene.git
The following commit(s) were added to refs/heads/branch_10x by this push:
new 71e8b21a8f4 Speed up scoring conjunctions a bit. (#14345)
71e8b21a8f4 is described below
commit 71e8b21a8f42825e24f1719a9f297aa8a3f8745b
Author: Adrien Grand <jp...@gmail.com>
AuthorDate: Thu Mar 13 14:11:15 2025 +0100
Speed up scoring conjunctions a bit. (#14345)
We currently pushe FILTER clauses as constant-scoring MUST clauses with a 0
score to `BlockMaxConjunctionBulkScorer`. This change improves efficiency a bit
by reducing polymorphism a bit (TermScorer / ConstantScoreScorer) and saving
checks of whether the score of the leading clause is high enough for the score
of the whole conjunction to be competitive when this leading clause is a
filter.
---
.../search/BlockMaxConjunctionBulkScorer.java | 47 ++++++++++++++--------
1 file changed, 31 insertions(+), 16 deletions(-)
diff --git a/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionBulkScorer.java b/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionBulkScorer.java
index 3022608233e..fd89153ffe6 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionBulkScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionBulkScorer.java
@@ -36,6 +36,7 @@ import org.apache.lucene.util.MathUtil;
final class BlockMaxConjunctionBulkScorer extends BulkScorer {
private final Scorer[] scorers;
+ private final Scorable[] scorables;
private final DocIdSetIterator[] iterators;
private final DocIdSetIterator lead1, lead2;
private final Scorable scorer1, scorer2;
@@ -49,12 +50,14 @@ final class BlockMaxConjunctionBulkScorer extends BulkScorer {
}
this.scorers = scorers.toArray(Scorer[]::new);
Arrays.sort(this.scorers, Comparator.comparingLong(scorer -> scorer.iterator().cost()));
+ this.scorables =
+ Arrays.stream(this.scorers).map(ScorerUtil::likelyTermScorer).toArray(Scorable[]::new);
this.iterators =
Arrays.stream(this.scorers).map(Scorer::iterator).toArray(DocIdSetIterator[]::new);
lead1 = ScorerUtil.likelyImpactsEnum(iterators[0]);
lead2 = ScorerUtil.likelyImpactsEnum(iterators[1]);
- scorer1 = ScorerUtil.likelyTermScorer(this.scorers[0]);
- scorer2 = ScorerUtil.likelyTermScorer(this.scorers[1]);
+ scorer1 = this.scorables[0];
+ scorer2 = this.scorables[1];
this.sumOfOtherClauses = new double[this.scorers.length];
for (int i = 0; i < sumOfOtherClauses.length; i++) {
sumOfOtherClauses[i] = Double.POSITIVE_INFINITY;
@@ -111,6 +114,19 @@ final class BlockMaxConjunctionBulkScorer extends BulkScorer {
if (lead1.docID() < min) {
lead1.advance(min);
}
+ if (lead1.docID() >= max) {
+ return;
+ }
+
+ Scorable scorer1 = this.scorer1;
+ if (scorers[0].getMaxScore(max - 1) == 0f) {
+ // Null out scorer1 if it may only produce 0 scores over this window. In practice, this is
+ // mostly useful because FILTER clauses are pushed as constant-scoring MUST clauses with a
+ // 0 score to this scorer. Setting it to null instead of using a different impl helps
+ // reduce polymorphism of calls to Scorable#score and skip the check of whether the leading
+ // clause produced a high-enough score for the doc to be competitive.
+ scorer1 = null;
+ }
final double sumOfOtherMaxScoresAt1 = sumOfOtherClauses[1];
@@ -127,21 +143,20 @@ final class BlockMaxConjunctionBulkScorer extends BulkScorer {
// frequencies.
final boolean hasMinCompetitiveScore = scorable.minCompetitiveScore > 0;
double currentScore;
- if (hasMinCompetitiveScore) {
+ if (scorer1 != null && hasMinCompetitiveScore) {
currentScore = scorer1.score();
- } else {
- currentScore = 0;
- }
- // This is the same logic as in the below for loop, specialized for the 2nd least costly
- // clause. This seems to help the JVM.
+ // This is the same logic as in the below for loop, specialized for the 2nd least costly
+ // clause. This seems to help the JVM.
- // First check if we have a chance of having a match based on max scores
- if (hasMinCompetitiveScore
- && (float) MathUtil.sumUpperBound(currentScore + sumOfOtherMaxScoresAt1, scorers.length)
- < scorable.minCompetitiveScore) {
- doc = lead1.nextDoc();
- continue advanceHead;
+ // First check if we have a chance of having a match based on max scores
+ if ((float) MathUtil.sumUpperBound(currentScore + sumOfOtherMaxScoresAt1, scorers.length)
+ < scorable.minCompetitiveScore) {
+ doc = lead1.nextDoc();
+ continue advanceHead;
+ }
+ } else {
+ currentScore = 0;
}
// NOTE: lead2 may be on `doc` already if we `continue`d on the previous loop iteration.
@@ -177,12 +192,12 @@ final class BlockMaxConjunctionBulkScorer extends BulkScorer {
}
assert iterators[i].docID() == doc;
if (hasMinCompetitiveScore) {
- currentScore += scorers[i].score();
+ currentScore += scorables[i].score();
}
}
if (hasMinCompetitiveScore == false) {
- for (Scorer scorer : scorers) {
+ for (Scorable scorer : scorables) {
currentScore += scorer.score();
}
}