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 2026/04/27 12:58:26 UTC

(lucene) branch main updated: Optimize DisjunctionMaxBulkScorer: reuse inner collector and inline score reset (#15868)

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 5d135e741d9 Optimize DisjunctionMaxBulkScorer: reuse inner collector and inline score reset (#15868)
5d135e741d9 is described below

commit 5d135e741d95a070c60a15426f4fb2825d94f5ee
Author: Prithvi <pr...@gmail.com>
AuthorDate: Mon Apr 27 18:28:20 2026 +0530

    Optimize DisjunctionMaxBulkScorer: reuse inner collector and inline score reset (#15868)
---
 lucene/CHANGES.txt                                 |  3 ++
 .../lucene/search/DisjunctionMaxBulkScorer.java    | 63 +++++++++++-----------
 2 files changed, 35 insertions(+), 31 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index a94ecc0bf15..2714afd31c5 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -129,6 +129,9 @@ Improvements
 
 Optimizations
 ---------------------
+* GITHUB#15868: Optimize DisjunctionMaxBulkScorer by reusing the inner LeafCollector across
+  sub-scorers and resetting windowScores inline during replay instead of Arrays.fill. (Prithvi S)
+
 * GITHUB#15681, GITHUB#15833: Replace pre-sized array or empty array with lambda expression to call Collection#toArray. (Zhou Hui)
 
 * GITHUB#13782: Replace handwritten loops compare with Arrays.compareUnsigned in TermsEnum and TermsEnumFrame classes. (Zhou Hui)
diff --git a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxBulkScorer.java b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxBulkScorer.java
index 25dd6ab7f3a..83e54489d87 100644
--- a/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxBulkScorer.java
+++ b/lucene/core/src/java/org/apache/lucene/search/DisjunctionMaxBulkScorer.java
@@ -17,7 +17,6 @@
 package org.apache.lucene.search;
 
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Objects;
@@ -46,6 +45,32 @@ final class DisjunctionMaxBulkScorer extends BulkScorer {
   private final float[] windowScores = new float[WINDOW_SIZE];
   private final PriorityQueue<BulkScorerAndNext> scorers;
   private final SimpleScorable topLevelScorable = new SimpleScorable();
+  private int windowMin;
+
+  /**
+   * Reusable inner {@link LeafCollector} that collects matches into the window bitset and scores
+   * array. This avoids allocating a new anonymous collector object per sub-scorer per window.
+   */
+  private final LeafCollector innerCollector =
+      new LeafCollector() {
+
+        private Scorable scorer;
+
+        @Override
+        public void setScorer(Scorable scorer) throws IOException {
+          this.scorer = scorer;
+          if (topLevelScorable.minCompetitiveScore != 0f) {
+            scorer.setMinCompetitiveScore(topLevelScorable.minCompetitiveScore);
+          }
+        }
+
+        @Override
+        public void collect(int doc) throws IOException {
+          final int delta = doc - windowMin;
+          windowMatches.set(delta);
+          windowScores[delta] = Math.max(windowScores[delta], scorer.score());
+        }
+      };
 
   DisjunctionMaxBulkScorer(List<BulkScorer> scorers) {
     if (scorers.size() < 2) {
@@ -61,51 +86,27 @@ final class DisjunctionMaxBulkScorer extends BulkScorer {
     BulkScorerAndNext top = scorers.top();
 
     while (top.next < max) {
-      final int windowMin = Math.max(top.next, min);
+      windowMin = Math.max(top.next, min);
       final int windowMax = MathUtil.unsignedMin(max, windowMin + WINDOW_SIZE);
 
       // First compute matches / scores in the window
       do {
-        top.next =
-            top.scorer.score(
-                new LeafCollector() {
-
-                  private Scorable scorer;
-
-                  @Override
-                  public void setScorer(Scorable scorer) throws IOException {
-                    this.scorer = scorer;
-                    if (topLevelScorable.minCompetitiveScore != 0f) {
-                      scorer.setMinCompetitiveScore(topLevelScorable.minCompetitiveScore);
-                    }
-                  }
-
-                  @Override
-                  public void collect(int doc) throws IOException {
-                    final int delta = doc - windowMin;
-                    windowMatches.set(delta);
-                    windowScores[delta] = Math.max(windowScores[delta], scorer.score());
-                  }
-                },
-                acceptDocs,
-                windowMin,
-                windowMax);
+        top.next = top.scorer.score(innerCollector, acceptDocs, windowMin, windowMax);
         top = scorers.updateTop();
       } while (top.next < windowMax);
 
-      // Then replay
+      // Then replay, resetting windowScores entries inline to avoid a full Arrays.fill
       collector.setScorer(topLevelScorable);
       for (int windowDoc = windowMatches.nextSetBit(0);
           windowDoc != DocIdSetIterator.NO_MORE_DOCS;
           windowDoc = windowMatches.nextSetBit(windowDoc + 1)) {
-        int doc = windowMin + windowDoc;
         topLevelScorable.score = windowScores[windowDoc];
-        collector.collect(doc);
+        windowScores[windowDoc] = 0f;
+        collector.collect(windowMin + windowDoc);
       }
 
-      // Finally clean up state
+      // Only the bitset needs clearing; windowScores entries were reset during replay above.
       windowMatches.clear();
-      Arrays.fill(windowScores, 0f);
     }
 
     return top.next;