-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Enable Block-Max WAND dynamic pruning for FunctionScoreQuery via DocValuesSkipper (+422% QPS) #16431
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
Open
rajat315315
wants to merge
15
commits into
apache:main
Choose a base branch
from
rajat315315:feature/function-score-wand-skip-index
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+483
−9
Open
Enable Block-Max WAND dynamic pruning for FunctionScoreQuery via DocValuesSkipper (+422% QPS) #16431
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
5eac48d
perf: implement advanceShallow and getMaxScore logic for FunctionScor…
rajat315315 fb8e293
Implement DocValuesSkipper WAND pruning for FunctionScoreQuery and ad…
rajat315315 29ef08c
Update benchmark to test indexSort
rajat315315 168517c
Update benchmark to use BooleanQuery disjunction base query
rajat315315 1222a4e
Set DEFAULT_SKIP_INDEX_INTERVAL_SIZE to 128
rajat315315 4a73cce
Keep DEFAULT_SKIP_INDEX_INTERVAL_SIZE at 4096 in FunctionScoreQuery PR
rajat315315 ad7c840
Commit benchmark class
rajat315315 21042fa
Add support for monotonically decreasing functions in DoubleValuesSource
rajat315315 aff2cf6
Fix EOF trailing newlines
rajat315315 1fa8c20
Deprecate 2-arg fromField and add Javadoc note per review
rajat315315 8f187e1
Using a custom homemade function without needing to specify inc/dec.
rajat315315 6dba6f7
Using Monotonicity as an Enum.
rajat315315 152ed7f
Removed deprecated method.
rajat315315 6a1cdb5
prek
rajat315315 b996adf
gradlew tidy
rajat315315 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
150 changes: 150 additions & 0 deletions
150
...jmh/src/java/org/apache/lucene/benchmark/jmh/FunctionScoreWANDMainVsFeatureBenchmark.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| /* | ||
| * 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.benchmark.jmh; | ||
|
|
||
| import java.io.IOException; | ||
| import java.util.Random; | ||
| import java.util.concurrent.TimeUnit; | ||
| import org.apache.lucene.document.Document; | ||
| import org.apache.lucene.document.Field; | ||
| import org.apache.lucene.document.NumericDocValuesField; | ||
| import org.apache.lucene.document.TextField; | ||
| import org.apache.lucene.index.DirectoryReader; | ||
| import org.apache.lucene.index.IndexReader; | ||
| import org.apache.lucene.index.IndexWriter; | ||
| import org.apache.lucene.index.IndexWriterConfig; | ||
| import org.apache.lucene.index.Term; | ||
| import org.apache.lucene.queries.function.FunctionScoreQuery; | ||
| import org.apache.lucene.search.BooleanQuery; | ||
| import org.apache.lucene.search.DoubleValuesSource; | ||
| import org.apache.lucene.search.IndexSearcher; | ||
| import org.apache.lucene.search.Query; | ||
| import org.apache.lucene.search.TermQuery; | ||
| import org.apache.lucene.search.TopDocs; | ||
| import org.apache.lucene.store.ByteBuffersDirectory; | ||
| import org.apache.lucene.store.Directory; | ||
| import org.openjdk.jmh.annotations.Benchmark; | ||
| import org.openjdk.jmh.annotations.BenchmarkMode; | ||
| import org.openjdk.jmh.annotations.Fork; | ||
| import org.openjdk.jmh.annotations.Level; | ||
| import org.openjdk.jmh.annotations.Measurement; | ||
| import org.openjdk.jmh.annotations.Mode; | ||
| import org.openjdk.jmh.annotations.OutputTimeUnit; | ||
| import org.openjdk.jmh.annotations.Param; | ||
| import org.openjdk.jmh.annotations.Scope; | ||
| import org.openjdk.jmh.annotations.Setup; | ||
| import org.openjdk.jmh.annotations.State; | ||
| import org.openjdk.jmh.annotations.TearDown; | ||
| import org.openjdk.jmh.annotations.Warmup; | ||
|
|
||
| /** | ||
| * JMH Micro-benchmark comparing actual FunctionScoreQuery search throughput on main branch vs | ||
| * feature/function-score-wand-skip-index branch over 1 Million Lucene index documents. | ||
| */ | ||
| @State(Scope.Benchmark) | ||
| @BenchmarkMode(Mode.Throughput) | ||
| @OutputTimeUnit(TimeUnit.SECONDS) | ||
| @Warmup(iterations = 2, time = 2) | ||
| @Measurement(iterations = 3, time = 3) | ||
| @Fork( | ||
| value = 1, | ||
| warmups = 1, | ||
| jvmArgsAppend = {"-Xmx8g", "-Xms8g"}) | ||
| public class FunctionScoreWANDMainVsFeatureBenchmark { | ||
|
|
||
| @State(Scope.Benchmark) | ||
| public static class BenchmarkState { | ||
|
|
||
| @Param({"1000000"}) | ||
| public int numDocs; | ||
|
|
||
| @Param({"true", "false"}) | ||
| public boolean indexSort; | ||
|
|
||
| @Param({"100"}) | ||
| public int topK; | ||
|
|
||
| public Directory dir; | ||
| public IndexReader reader; | ||
| public IndexSearcher searcher; | ||
| public Query functionScoreQuery; | ||
|
|
||
| @Setup(Level.Trial) | ||
| public void setup() throws IOException { | ||
| dir = new ByteBuffersDirectory(); | ||
| IndexWriterConfig iwc = new IndexWriterConfig(); | ||
| iwc.setRAMBufferSizeMB(256); | ||
| if (indexSort) { | ||
| iwc.setIndexSort( | ||
| new org.apache.lucene.search.Sort( | ||
| new org.apache.lucene.search.SortField( | ||
| "score_field", org.apache.lucene.search.SortField.Type.LONG, true))); | ||
| } | ||
|
|
||
| try (IndexWriter writer = new IndexWriter(dir, iwc)) { | ||
| Random random = new Random(42); | ||
| String[] terms = {"term_a", "term_b", "term_c", "term_d", "term_e"}; | ||
| for (int i = 0; i < numDocs; i++) { | ||
| Document doc = new Document(); | ||
| String chosenTerm = terms[random.nextInt(terms.length)]; | ||
| doc.add(new TextField("body", chosenTerm, Field.Store.NO)); | ||
| // Real-world Power-law / Zipfian score distribution (98% low scores, 2% high scores) | ||
| long scoreVal = | ||
| (random.nextFloat() < 0.02f) ? (50000 + random.nextInt(50000)) : random.nextInt(100); | ||
| doc.add(new NumericDocValuesField("score_field", scoreVal)); | ||
| writer.addDocument(doc); | ||
| } | ||
| writer.commit(); | ||
| } | ||
|
|
||
| reader = DirectoryReader.open(dir); | ||
| searcher = new IndexSearcher(reader); | ||
|
|
||
| BooleanQuery.Builder bq = new BooleanQuery.Builder(); | ||
| bq.add( | ||
| new TermQuery(new Term("body", "term_a")), | ||
| org.apache.lucene.search.BooleanClause.Occur.SHOULD); | ||
| bq.add( | ||
| new TermQuery(new Term("body", "term_b")), | ||
| org.apache.lucene.search.BooleanClause.Occur.SHOULD); | ||
| bq.add( | ||
| new TermQuery(new Term("body", "term_c")), | ||
| org.apache.lucene.search.BooleanClause.Occur.SHOULD); | ||
| bq.add( | ||
| new TermQuery(new Term("body", "term_d")), | ||
| org.apache.lucene.search.BooleanClause.Occur.SHOULD); | ||
| bq.add( | ||
| new TermQuery(new Term("body", "term_e")), | ||
| org.apache.lucene.search.BooleanClause.Occur.SHOULD); | ||
| Query baseQuery = bq.build(); | ||
| DoubleValuesSource valueSource = DoubleValuesSource.fromLongField("score_field"); | ||
| functionScoreQuery = new FunctionScoreQuery(baseQuery, valueSource); | ||
| } | ||
|
|
||
| @TearDown(Level.Trial) | ||
| public void tearDown() throws IOException { | ||
| reader.close(); | ||
| dir.close(); | ||
| } | ||
| } | ||
|
|
||
| @Benchmark | ||
| public TopDocs searchFunctionScoreQuery(BenchmarkState state) throws IOException { | ||
| return state.searcher.search(state.functionScoreQuery, state.topK); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 think we do not need strictly increasing, rather
NONDECREASINGandNONINCREASING, right? what if the function is constant?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.
Also, if the function is a composite function, like
sum, it is non-increasing when both of its inputs are, but non-decreasing when both of its inputs are, but when one is decreasing and the other one increasing, it is undefined. I don't know how we can really expect this to work with general functionsThere 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 don't like to have "NON" in the name, this makes it hard to use. INCREASING should be fine, because unless it is strictly increasing it means that the values go up. A constant function is fine, you should be able to chooseany of INCREASING or DECREASING, correct?
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 think in those cases where we are uncertain about the monotonicity of a function, we can use
Monotonicity.NONE.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.
Wikipedia says:
Because of the
<=in that definitiion "monitonically increasing means" that the function result may be constant.With strictly increasing the definition is:
Uh oh!
There was an error while loading. Please reload this page.
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.
So I think terms are correct. Constant function is allowed and can be classified as both increasing or decreasing. It would also work fine in our code - you can pass both enum constants, because in case of a constant function the
getMinScore()andgetMaxScore()would both return the same value (the constant), so the result for a block with constant function is same for both increasing or decreasing.Uh oh!
There was an error while loading. Please reload this page.
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.
In case of constant scoring, my view-point is a little different.
Since all docs have the same score, we need to evaluate each and every doc. We can't prune any block.
In that case, we might need to use,
Monotonicity.NONEUh oh!
There was an error while loading. Please reload this page.
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 think it would still work if it is constant. In that case it would see the first doc with the constant value. After that it asks to skip, but provides the constant value. The code then needs to revisit all docs, because the comparison to min and max score needs to revisit all docs with exact same score. It can only exclude docs with larger or less value (that don't exist).
Maybe add a test. 😜
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.
thanks for sharing the definitions, OK by me to drop the
NON