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();
         }
       }