jimczi commented on a change in pull request #1725:
URL: https://github.com/apache/lucene-solr/pull/1725#discussion_r474232065



##########
File path: lucene/core/src/java/org/apache/lucene/search/FieldComparator.java
##########
@@ -136,6 +136,13 @@ public int compareValues(T first, T second) {
     }
   }
 
+  /**
+   * Informs the comparator that sorting is done in reverse.
+   * This is necessary only for skipping functionality.
+   */
+  public void setReverse() {

Review comment:
       I don't understand why this function is needed ? Can't you just pass the 
information when the `DocComparator` is created in the SortField ?

##########
File path: 
lucene/core/src/java/org/apache/lucene/search/FilteringLeafFieldComparator.java
##########
@@ -32,8 +33,22 @@
   DocIdSetIterator competitiveIterator() throws IOException;
 
   /**
-   * Informs this leaf comparator that it is allowed to start updating its 
competitive iterator.
-   * This method is called from a collector when queue becomes full and 
threshold is reached.
+   * Informs this leaf comparator that hits threshold is reached.
+   * This method is called from a collector when hits threshold is reached.
+   * For some filtering comparators (e.g. {@code FilteringDocLeafComparator} 
reaching
+   * hits threshold is enough to start updating their iterators, even when 
queue is not yet full.
    */
-  void setCanUpdateIterator() throws IOException;
+  void setHitsThresholdReached() throws IOException;
+
+  /**
+   * Informs this leaf comparator that queue has become full.
+   * This method is called from a collector when queue becomes full.
+   */
+  void setQueueFull() throws IOException;

Review comment:
       That seems redundant. I think we can add a single method: 
setHitsThresholdReached(int). We have all the other contexts needed in the 
LeafFieldComparator. The complexity is that `setBottom `and 
`setHitsThresholdReached(int)` are called on the leaves even though they are 
global events. Maybe forcing the hierarchy of the filtering fields to use a 
`SimpleFieldComparator` could simplify things ? This way you don't have to 
repeat redundant informations (hits threshold and queue full) on each leave.

##########
File path: 
lucene/core/src/java/org/apache/lucene/search/comparators/DocComparator.java
##########
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.search.comparators;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.FilteringLeafFieldComparator;
+import org.apache.lucene.search.LeafFieldComparator;
+import org.apache.lucene.search.Scorable;
+
+import java.io.IOException;
+
+/**
+ * Comparator that sorts by asc _doc
+ */
+public class DocComparator extends FieldComparator<Integer> {
+    private final int[] docIDs;
+    private int topValue;
+    private boolean topValueSet;
+    private boolean reverse = false; // only used to check if skipping 
functionality should be enabled
+
+    /** Creates a new comparator based on document ids for {@code numHits} */
+    public DocComparator(int numHits) {
+        docIDs = new int[numHits];
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+        // No overflow risk because docIDs are non-negative
+        return docIDs[slot1] - docIDs[slot2];
+    }
+
+
+    @Override
+    public LeafFieldComparator getLeafComparator(LeafReaderContext context) {
+        // TODO: can we "map" our docIDs to the current
+        // reader? saves having to then subtract on every
+        // compare call
+        return new DocLeafComparator(context);
+    }
+
+    @Override
+    public void setTopValue(Integer value) {
+        topValue = value;
+        topValueSet = true;
+    }
+
+    @Override
+    public Integer value(int slot) {
+        return Integer.valueOf(docIDs[slot]);
+    }
+
+    @Override
+    public void setReverse() {
+        reverse = true;
+    }
+
+
+    /**
+     * DocLeafComparator with skipping functionality.
+     * When sort by _doc asc and "after" document is set,
+     * the comparator provides an iterator that can quickly skip to the 
desired "after" document.
+     */
+    private class DocLeafComparator implements FilteringLeafFieldComparator {
+        private final int docBase;
+        private int bottom;
+
+        private final boolean enableSkipping;
+        private final int minDoc;
+        private final int maxDoc;
+        private DocIdSetIterator topValueIterator; // iterator that starts 
from topValue
+
+        private boolean iteratorUpdated = false;
+
+        public DocLeafComparator(LeafReaderContext context) {
+            this.docBase = context.docBase;
+            // skipping functionality is enabled if topValue is set and sort 
is asc
+            this.enableSkipping = topValueSet && reverse == false ? true: 
false;
+            if (enableSkipping) {
+                this.minDoc = topValue + 1;
+                this.maxDoc = context.reader().maxDoc();
+                this.topValueIterator = DocIdSetIterator.all(maxDoc);
+            } else {
+                this.minDoc = -1;
+                this.maxDoc = -1;
+                this.topValueIterator = null;
+            }
+        }
+
+        @Override
+        public void setBottom(int slot) {
+            bottom = docIDs[slot];

Review comment:
       We should be able to early terminate here if the total hits threshold 
has been reached. If it's not reached yet, we can early terminate later in 
`setHitsThresholdReached`.

##########
File path: lucene/core/src/java/org/apache/lucene/search/TopFieldCollector.java
##########
@@ -150,11 +150,15 @@ public void setScorer(Scorable scorer) throws IOException 
{
       if (minScoreAcc != null) {
         updateGlobalMinCompetitiveScore(scorer);
       }
-      if (filteringLeafComparator != null && queueFull && 
hitsThresholdChecker.isThresholdReached()) {
-        // if queue became full and hitsThreshold was reached in previous 
segments,
-        // notify this segment's leaf comparator that its competitive iterator 
can be updated
-        filteringLeafComparator.setCanUpdateIterator();
-        totalHitsRelation = TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO;
+
+      if (filteringLeafComparator != null && 
hitsThresholdChecker.isThresholdReached()) {
+        // hitsThreshold was reached in previous segments, notify this 
segment's leaf comparator about it
+        filteringLeafComparator.setHitsThresholdReached();
+        // if queue became full in previous segments, notify this segment's 
leaf comparator about it
+        if (queueFull) filteringLeafComparator.setQueueFull();
+        if (filteringLeafComparator.iteratorUpdated()) {
+          totalHitsRelation = TotalHits.Relation.GREATER_THAN_OR_EQUAL_TO;
+        }

Review comment:
       See my previous comment, I think we should call 
`setHitsThresholdReached` once, when the threshold is reached. 
   Same for `setQueueFull` that is already call in `setBottom`. Said 
differently, this part could be entirely removed ;).

##########
File path: 
lucene/core/src/java/org/apache/lucene/search/FilteringLeafFieldComparator.java
##########
@@ -32,8 +33,22 @@
   DocIdSetIterator competitiveIterator() throws IOException;
 
   /**
-   * Informs this leaf comparator that it is allowed to start updating its 
competitive iterator.
-   * This method is called from a collector when queue becomes full and 
threshold is reached.
+   * Informs this leaf comparator that hits threshold is reached.
+   * This method is called from a collector when hits threshold is reached.
+   * For some filtering comparators (e.g. {@code FilteringDocLeafComparator} 
reaching
+   * hits threshold is enough to start updating their iterators, even when 
queue is not yet full.
    */
-  void setCanUpdateIterator() throws IOException;
+  void setHitsThresholdReached() throws IOException;
+
+  /**
+   * Informs this leaf comparator that queue has become full.
+   * This method is called from a collector when queue becomes full.
+   */
+  void setQueueFull() throws IOException;
+
+  /**
+   * Returns {@code true} if the competitive iterator is updated.
+   * This tells the calling collector that it can update the {@code 
TotalHits.Relation} to {GREATER_THAN_OR_EQUAL_TO}
+   */
+  boolean iteratorUpdated() throws IOException;

Review comment:
       Is  it really needed ? Maybe we should set `GREATER_THAN_OR_EQUAL_TO` 
every time a TOP_SCORES collection reaches  the total hits threshold ?

##########
File path: 
lucene/core/src/java/org/apache/lucene/search/comparators/DocComparator.java
##########
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.lucene.search.comparators;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.DocIdSetIterator;
+import org.apache.lucene.search.FieldComparator;
+import org.apache.lucene.search.FilteringLeafFieldComparator;
+import org.apache.lucene.search.LeafFieldComparator;
+import org.apache.lucene.search.Scorable;
+
+import java.io.IOException;
+
+/**
+ * Comparator that sorts by asc _doc
+ */
+public class DocComparator extends FieldComparator<Integer> {
+    private final int[] docIDs;
+    private int topValue;
+    private boolean topValueSet;
+    private boolean reverse = false; // only used to check if skipping 
functionality should be enabled
+
+    /** Creates a new comparator based on document ids for {@code numHits} */
+    public DocComparator(int numHits) {
+        docIDs = new int[numHits];
+    }
+
+    @Override
+    public int compare(int slot1, int slot2) {
+        // No overflow risk because docIDs are non-negative
+        return docIDs[slot1] - docIDs[slot2];
+    }
+
+
+    @Override
+    public LeafFieldComparator getLeafComparator(LeafReaderContext context) {
+        // TODO: can we "map" our docIDs to the current
+        // reader? saves having to then subtract on every
+        // compare call
+        return new DocLeafComparator(context);
+    }
+
+    @Override
+    public void setTopValue(Integer value) {
+        topValue = value;
+        topValueSet = true;
+    }
+
+    @Override
+    public Integer value(int slot) {
+        return Integer.valueOf(docIDs[slot]);
+    }
+
+    @Override
+    public void setReverse() {
+        reverse = true;
+    }
+
+
+    /**
+     * DocLeafComparator with skipping functionality.
+     * When sort by _doc asc and "after" document is set,
+     * the comparator provides an iterator that can quickly skip to the 
desired "after" document.
+     */
+    private class DocLeafComparator implements FilteringLeafFieldComparator {
+        private final int docBase;
+        private int bottom;
+
+        private final boolean enableSkipping;
+        private final int minDoc;
+        private final int maxDoc;
+        private DocIdSetIterator topValueIterator; // iterator that starts 
from topValue
+
+        private boolean iteratorUpdated = false;
+
+        public DocLeafComparator(LeafReaderContext context) {
+            this.docBase = context.docBase;
+            // skipping functionality is enabled if topValue is set and sort 
is asc
+            this.enableSkipping = topValueSet && reverse == false ? true: 
false;
+            if (enableSkipping) {
+                this.minDoc = topValue + 1;
+                this.maxDoc = context.reader().maxDoc();
+                this.topValueIterator = DocIdSetIterator.all(maxDoc);
+            } else {
+                this.minDoc = -1;
+                this.maxDoc = -1;
+                this.topValueIterator = null;
+            }
+        }
+
+        @Override
+        public void setBottom(int slot) {
+            bottom = docIDs[slot];

Review comment:
       Any query sorted by doc id can early terminate after N matches. That's 
an important aspect of the optimization since it can be handled by the hits 
threshold transparently. If there is no `after` value, the threshold should be 
an upper bound of the number of document that we will collect in the comparator.




----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: issues-unsubscr...@lucene.apache.org
For additional commands, e-mail: issues-h...@lucene.apache.org

Reply via email to