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/06/03 05:30:19 UTC

(lucene) 01/05: Keep evaluating conjunction one doc-at-a-time until dynamic pruning kicks in. (#14739)

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

commit 92de7a200751132e3470284c78793ce2f9aa477c
Author: Adrien Grand <jp...@gmail.com>
AuthorDate: Mon Jun 2 17:03:05 2025 +0200

    Keep evaluating conjunction one doc-at-a-time until dynamic pruning kicks in. (#14739)
    
    This essentially reverts the change from #14701 for conjunctive queries that
    have not reached their `totalHitsThreshold` yet. This should speed up queries
    whose total number of matches is in the order of `totalHitsThreshold` or less,
    such as filtered conjunctions on nightly benchmarks.
---
 .../search/BlockMaxConjunctionBulkScorer.java      | 66 ++++++++++++++++++----
 1 file changed, 55 insertions(+), 11 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 7091777a262..e2e063feb5e 100644
--- a/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionBulkScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/BlockMaxConjunctionBulkScorer.java
@@ -87,18 +87,64 @@ final class BlockMaxConjunctionBulkScorer extends BulkScorer {
       // NOTE: windowMax is inclusive
       int windowMax = Math.min(scorers[0].advanceShallow(windowMin), max - 1);
 
-      float maxWindowScore = Float.POSITIVE_INFINITY;
       if (0 < scorable.minCompetitiveScore) {
-        maxWindowScore = computeMaxScore(windowMin, windowMax);
+        float maxWindowScore = computeMaxScore(windowMin, windowMax);
+        scoreWindowScoreFirst(collector, acceptDocs, windowMin, windowMax + 1, maxWindowScore);
+      } else {
+        scoreWindowDocFirst(collector, acceptDocs, windowMin, windowMax + 1);
       }
-      scoreWindow(collector, acceptDocs, windowMin, windowMax + 1, maxWindowScore);
       windowMin = Math.max(lead.docID(), windowMax + 1);
     }
 
     return windowMin >= maxDoc ? DocIdSetIterator.NO_MORE_DOCS : windowMin;
   }
 
-  private void scoreWindow(
+  /**
+   * Score a window of doc IDs by first finding agreement between all iterators, and only then
+   * compute scores and call the collector.
+   */
+  private void scoreWindowDocFirst(LeafCollector collector, Bits acceptDocs, int min, int max)
+      throws IOException {
+    int doc = lead.docID();
+    if (doc < min) {
+      doc = lead.advance(min);
+    }
+
+    outer:
+    while (doc < max) {
+      if (acceptDocs == null || acceptDocs.get(doc)) {
+        for (int i = 1; i < iterators.length; ++i) {
+          DocIdSetIterator iterator = iterators[i];
+          int otherDoc = iterator.docID();
+          if (otherDoc < doc) {
+            otherDoc = iterator.advance(doc);
+          }
+          if (doc != otherDoc) {
+            doc = lead.advance(otherDoc);
+            continue outer;
+          }
+        }
+
+        double score = 0;
+        for (Scorable scorable : scorables) {
+          score += scorable.score();
+        }
+        scorable.score = (float) score;
+        collector.collect(doc);
+      }
+      doc = lead.nextDoc();
+    }
+  }
+
+  /**
+   * Score a window of doc IDs by computing matches and scores on the lead costly clause, then
+   * iterate other clauses one by one to remove documents that do not match and increase the global
+   * score by the score of the current clause. This is often faster when a minimum competitive score
+   * is set, as score computations can be more efficient (e.g. thanks to vectorization) and because
+   * we can skip advancing other clauses if the global score so far is not high enough for a doc to
+   * have a chance of being competitive.
+   */
+  private void scoreWindowScoreFirst(
       LeafCollector collector, Bits acceptDocs, int min, int max, float maxWindowScore)
       throws IOException {
     if (maxWindowScore < scorable.minCompetitiveScore) {
@@ -120,13 +166,11 @@ final class BlockMaxConjunctionBulkScorer extends BulkScorer {
       docAndScoreAccBuffer.copyFrom(docAndScoreBuffer);
 
       for (int i = 1; i < scorers.length; ++i) {
-        if (scorable.minCompetitiveScore > 0) {
-          ScorerUtil.filterCompetitiveHits(
-              docAndScoreAccBuffer,
-              sumOfOtherClauses[i],
-              scorable.minCompetitiveScore,
-              scorers.length);
-        }
+        ScorerUtil.filterCompetitiveHits(
+            docAndScoreAccBuffer,
+            sumOfOtherClauses[i],
+            scorable.minCompetitiveScore,
+            scorers.length);
 
         ScorerUtil.applyRequiredClause(docAndScoreAccBuffer, iterators[i], scorables[i]);
       }