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 2023/10/05 10:47:05 UTC

[lucene] branch main updated: Compute better windows in MaxScoreBulkScorer. (#12593)

This is an automated email from the ASF dual-hosted git repository.

jpountz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/main by this push:
     new 91019d685ad Compute better windows in MaxScoreBulkScorer. (#12593)
91019d685ad is described below

commit 91019d685adf49231dc13e37282d87e604275bf8
Author: Adrien Grand <jp...@gmail.com>
AuthorDate: Thu Oct 5 12:46:57 2023 +0200

    Compute better windows in MaxScoreBulkScorer. (#12593)
    
    MaxScoreBulkScorer computes windows based on the set of clauses that were
    essential in the *previous* window. This usually works well as the set of
    essential clauses tends to be stable over time, but there are cases when
    clauses get swapped between essential and non-essential clauses, and computing
    windows based on the previous window can lead to suboptimal choices.
    
    This PR creates a first proposal for the next score window using essential
    clauses from the previous window, and then creates a second proposal once
    scorers have been partitioned and their max scores have been updated. If this
    second proposal results in a smaller window, it gets used.
    
    On one particular query (`the incredibles`) and a reordered index with BP
    (which increases chances that scorers move from essential to non-essential or
    vice-versa), this change yielded a 2.3x speedup.
---
 .../apache/lucene/search/MaxScoreBulkScorer.java   | 50 ++++++++++++++++------
 1 file changed, 37 insertions(+), 13 deletions(-)

diff --git a/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java b/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java
index 4cc14cdd0b3..3c3f3db7491 100644
--- a/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/MaxScoreBulkScorer.java
@@ -76,12 +76,32 @@ final class MaxScoreBulkScorer extends BulkScorer {
     int outerWindowMin = min;
     outer:
     while (outerWindowMin < max) {
-      int outerWindowMax = updateMaxWindowScores(outerWindowMin);
+      int outerWindowMax = computeOuterWindowMax(outerWindowMin);
       outerWindowMax = Math.min(outerWindowMax, max);
-      if (partitionScorers() == false) {
-        // No matches in this window
-        outerWindowMin = outerWindowMax;
-        continue;
+
+      while (true) {
+        updateMaxWindowScores(outerWindowMin, outerWindowMax);
+        if (partitionScorers() == false) {
+          // No matches in this window
+          outerWindowMin = outerWindowMax;
+          continue outer;
+        }
+
+        // There is a dependency between windows and maximum scores, as we compute windows based on
+        // maximum scores and maximum scores based on windows.
+        // So the approach consists of starting by computing a window based on the set of essential
+        // scorers from the _previous_ window and then iteratively recompute maximum scores and
+        // windows as long as the window size decreases.
+        // In general the set of essential scorers is rather stable over time so this would exit
+        // after a single iteration, but there is a change that some scorers got swapped between the
+        // set of essential and non-essential scorers, in which case there may be multiple
+        // iterations of this loop.
+
+        int newOuterWindowMax = computeOuterWindowMax(outerWindowMin);
+        if (newOuterWindowMax >= outerWindowMax) {
+          break;
+        }
+        outerWindowMax = newOuterWindowMax;
       }
 
       DisiWrapper top = essentialQueue.top();
@@ -191,35 +211,39 @@ final class MaxScoreBulkScorer extends BulkScorer {
     }
   }
 
-  private int updateMaxWindowScores(int windowMin) throws IOException {
+  private int computeOuterWindowMax(int windowMin) throws IOException {
     // Only use essential scorers to compute the window's max doc ID, in order to avoid constantly
     // recomputing max scores over small windows
     final int firstWindowLead = Math.min(firstEssentialScorer, allScorers.length - 1);
-    for (int i = 0; i < firstWindowLead; ++i) {
-      final DisiWrapper scorer = allScorers[i];
-      if (scorer.doc < windowMin) {
-        scorer.scorer.advanceShallow(windowMin);
-      }
-    }
     int windowMax = DocIdSetIterator.NO_MORE_DOCS;
     for (int i = firstWindowLead; i < allScorers.length; ++i) {
       final DisiWrapper scorer = allScorers[i];
       final int upTo = scorer.scorer.advanceShallow(Math.max(scorer.doc, windowMin));
       windowMax = (int) Math.min(windowMax, upTo + 1L); // upTo is inclusive
     }
+
     // Score at least an entire inner window of docs
     windowMax =
         Math.max(
             windowMax, (int) Math.min(Integer.MAX_VALUE, (long) windowMin + INNER_WINDOW_SIZE));
 
+    return windowMax;
+  }
+
+  private void updateMaxWindowScores(int windowMin, int windowMax) throws IOException {
     for (DisiWrapper scorer : allScorers) {
       if (scorer.doc < windowMax) {
+        if (scorer.doc < windowMin) {
+          // Make sure to advance shallow if necessary to get as good score upper bounds as
+          // possible.
+          scorer.scorer.advanceShallow(windowMin);
+        }
         scorer.maxWindowScore = scorer.scorer.getMaxScore(windowMax - 1);
       } else {
+        // This scorer has no documents in the considered window.
         scorer.maxWindowScore = 0;
       }
     }
-    return windowMax;
   }
 
   private void scoreNonEssentialClauses(LeafCollector collector, int doc, double essentialScore)