-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Refactor main top-n bulk scorers to evaluate hits in a more term-at-a-time fashion. #14701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…-time fashion. `MaxScoreBulkScorer` and `BlockMaxConjunctionBulkScorer` currently evaluate hits in a doc-at-a-time (DAAT) fashion, meaning that they they look at all their clauses to find the next doc and so forth until all docs from the window are evaluated. This changes evaluation to run in a more term-at-a-time fashion (TAAT) within scoring windows, meaning that each clause is fully evaluated within the window before moving on to the next clause. Note that this isn't completely new, `BooleanScorer` has been doing this to exhaustively evaluate disjunctive queries, by loading their matches into a bit set, one clause at a time. Also note that this is a bit different from traditional TAAT as this is scoped to small-ish windows of doc IDs, not the entire doc ID space. This in-turn allows these scorers to take advantage of the new `Scorer#nextDocsAndScores` API, and provides a good speedup. A downside is that we may need to perform more memory copying in some cases, and evaluate a bit more documents, but the change still looks like a win in general.
This PR does not have an entry in lucene/CHANGES.txt. Consider adding one. If the PR doesn't need a changelog entry, then add the skip-changelog-check label to it and you will stop receiving this reminder on future updates to the PR. |
Filtered queries get a slowdown, but some important queries get a big speedup:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it, simpler and faster!
@@ -55,9 +55,6 @@ final class BlockMaxConjunctionBulkScorer extends BulkScorer { | |||
this.iterators = | |||
Arrays.stream(this.scorers).map(Scorer::iterator).toArray(DocIdSetIterator[]::new); | |||
lead1 = ScorerUtil.likelyImpactsEnum(iterators[0]); | |||
lead2 = ScorerUtil.likelyImpactsEnum(iterators[1]); | |||
scorer1 = this.scorables[0]; | |||
scorer2 = this.scorables[1]; | |||
this.sumOfOtherClauses = new double[this.scorers.length]; | |||
for (int i = 0; i < sumOfOtherClauses.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not introduced in this PR, but maybe change this to a Arrays.fill
by the way :)
@@ -38,11 +37,12 @@ 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; | |||
private final DocIdSetIterator lead1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe just name it lead
since we do not have lead2
now.
|
||
doc = lead1.nextDoc(); | ||
int maxOtherDoc = -1; | ||
for (int i = 0; i < iterators.length; ++i) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not important but since we are naming maxOtherDoc
let's start i
from 1?
if (scorer.doc == doc) { | ||
score += scorer.scorable.score(); | ||
} | ||
ScorerUtil.filterCompetitiveHits( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we skip this if minCompetitiveScore == 0f
, like other place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be needed as this evaluates non-essential clauses, and there can only be non-essential clauses if minCompetitiveScore > 0. I'll add an assertion to make this clearer.
if (curDoc < targetDoc) { | ||
curDoc = iterator.advance(targetDoc); | ||
} | ||
if (curDoc == targetDoc) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, could VectorUtil#findNextGTE
help here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wondered the same. :) Let's keep it this way (slightly simpler) and look into whether this can be made faster with VectorUtil in a follow-up?
@@ -118,98 +115,36 @@ private void scoreWindow( | |||
return; | |||
} | |||
|
|||
Scorable scorer1 = this.scorer1; | |||
if (scorers[0].getMaxScore(max - 1) == 0f) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was wondering why the FilteredAnd
tasks, which use BlockMaxConjunctionBulkScorer
, are getting more slow down than other tasks. Could the removal of this special optimization be the reason? Do we intend to remove this for simplification?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This may be a contributing factor indeed. I suspect that another factor is that filtered tasks use the default impl of nextDocsAndScores
which adds some overhead compared to what we were doing before since it first needs to copy matching docs to an array before doing work on this array. I was keen to looking into the performance of filtered tasks in a follow-up, I worry it's not going to be easy to recover performance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you!
…-time fashion. (#14701) `MaxScoreBulkScorer` and `BlockMaxConjunctionBulkScorer` currently evaluate hits in a doc-at-a-time (DAAT) fashion, meaning that they they look at all their clauses to find the next doc and so forth until all docs from the window are evaluated. This changes evaluation to run in a more term-at-a-time fashion (TAAT) within scoring windows, meaning that each clause is fully evaluated within the window before moving on to the next clause. Note that this isn't completely new, `BooleanScorer` has been doing this to exhaustively evaluate disjunctive queries, by loading their matches into a bit set, one clause at a time. Also note that this is a bit different from traditional TAAT as this is scoped to small-ish windows of doc IDs, not the entire doc ID space. This in-turn allows these scorers to take advantage of the new `Scorer#nextDocsAndScores` API, and provides a good speedup. A downside is that we may need to perform more memory copying in some cases, and evaluate a bit more documents, but the change still looks like a win in general.
* main: (32 commits) update os.makedirs with pathlib mkdir (apache#14710) Optimize AbstractKnnVectorQuery#createBitSet with intoBitset (apache#14674) Implement #docIDRunEnd() on PostingsEnum. (apache#14693) Speed up TermQuery (apache#14709) Refactor main top-n bulk scorers to evaluate hits in a more term-at-a-time fashion. (apache#14701) Fix WindowsFS test failure seen on Policeman Jenkins (apache#14706) Use a temporary repository location to download certain ecj versions ("drops") (apache#14703) Add assumption to ignore occasional test failures due to disconnected graphs (apache#14696) Return MatchNoDocsQuery when IndexOrDocValuesQuery::rewrite does not match (apache#14700) Minor access modifier adjustment to a couple of lucene90 backward compat types (apache#14695) Speed up exhaustive evaluation. (apache#14679) Specify and test that IOContext is immutable (apache#14686) deps(java): bump org.gradle.toolchains.foojay-resolver-convention (apache#14691) deps(java): bump org.eclipse.jgit:org.eclipse.jgit (apache#14692) Clean up how the test framework creates asserting scorables. (apache#14452) Make competitive iterators more robust. (apache#14532) Remove DISIDocIdStream. (apache#14550) Implement AssertingPostingsEnum#intoBitSet. (apache#14675) Fix patience knn queries to work with seeded knn queries (apache#14688) Added toString() method to BytesRefBuilder (apache#14676) ...
MaxScoreBulkScorer
andBlockMaxConjunctionBulkScorer
currently evaluate hits in a doc-at-a-time (DAAT) fashion, meaning that they they look at all their clauses to find the next doc and so forth until all docs from the window are evaluated. This changes evaluation to run in a more term-at-a-time fashion (TAAT) within scoring windows, meaning that each clause is fully evaluated within the window before moving on to the next clause.Note that this isn't completely new,
BooleanScorer
has been doing this to exhaustively evaluate disjunctive queries, by loading their matches into a bit set, one clause at a time. Also note that this is a bit different from traditional TAAT as this is scoped to small-ish windows of doc IDs, not the entire doc ID space.This in-turn allows these scorers to take advantage of the new
Scorer#nextDocsAndScores
API, and provides a good speedup. A downside is that we may need to perform more memory copying in some cases, and evaluate a bit more documents, but the change still looks like a win in general.