From 23a3db8bb6f24baf5da926d078819989d281b691 Mon Sep 17 00:00:00 2001 From: Adrien Grand Date: Thu, 6 Aug 2015 15:04:48 +0200 Subject: [PATCH] Speed up the `function_score` query when scores are not needed. This change improves the `function_score` query to not compute scores at all when they are not needed, and to not compute scores on the underlying query when the combine function is to replace the score with the scores of the functions. --- .../search/function/BoostScoreFunction.java | 5 +++ .../function/FieldValueFactorFunction.java | 5 +++ .../search/function/FunctionScoreQuery.java | 31 +++++++++++++------ .../search/function/RandomScoreFunction.java | 4 +++ .../lucene/search/function/ScoreFunction.java | 7 +++++ .../search/function/ScriptScoreFunction.java | 7 +++++ .../search/function/WeightFactorFunction.java | 10 ++++++ .../functionscore/DecayFunctionParser.java | 10 ++++++ 8 files changed, 70 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/BoostScoreFunction.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/BoostScoreFunction.java index 58d438adb3a55..13b4526128494 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/BoostScoreFunction.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/BoostScoreFunction.java @@ -57,6 +57,11 @@ public Explanation explainScore(int docId, Explanation subQueryScore) { }; } + @Override + public boolean needsScores() { + return false; + } + @Override public String toString() { return "boost[" + boost + "]"; diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/FieldValueFactorFunction.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/FieldValueFactorFunction.java index 8aa4897a8e08f..488c5fec603b6 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/FieldValueFactorFunction.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/FieldValueFactorFunction.java @@ -91,6 +91,11 @@ public Explanation explainScore(int docId, Explanation subQueryScore) { }; } + @Override + public boolean needsScores() { + return false; + } + /** * The Type class encapsulates the modification types that can be applied * to the score/value product. diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java index 1f8eb8edf0b6e..b3ad83e4d21d4 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/FunctionScoreQuery.java @@ -44,7 +44,7 @@ public class FunctionScoreQuery extends Query { public FunctionScoreQuery(Query subQuery, ScoreFunction function, Float minScore) { this.subQuery = subQuery; this.function = function; - this.combineFunction = function == null? combineFunction.MULT : function.getDefaultScoreCombiner(); + this.combineFunction = function == null? CombineFunction.MULT : function.getDefaultScoreCombiner(); this.minScore = minScore; } @@ -87,19 +87,27 @@ public Query rewrite(IndexReader reader) throws IOException { @Override public Weight createWeight(IndexSearcher searcher, boolean needsScores) throws IOException { - // TODO: needsScores - // if we don't need scores, just return the underlying weight? - Weight subQueryWeight = subQuery.createWeight(searcher, needsScores); - return new CustomBoostFactorWeight(this, subQueryWeight); + if (needsScores == false) { + return subQuery.createWeight(searcher, needsScores); + } + + boolean subQueryNeedsScores = + combineFunction != CombineFunction.REPLACE // if we don't replace we need the original score + || function == null // when the function is null, we just multiply the score, so we need it + || function.needsScores(); // some scripts can replace with a script that returns eg. 1/_score + Weight subQueryWeight = subQuery.createWeight(searcher, subQueryNeedsScores); + return new CustomBoostFactorWeight(this, subQueryWeight, subQueryNeedsScores); } class CustomBoostFactorWeight extends Weight { final Weight subQueryWeight; + final boolean needsScores; - public CustomBoostFactorWeight(Query parent, Weight subQueryWeight) throws IOException { + public CustomBoostFactorWeight(Query parent, Weight subQueryWeight, boolean needsScores) throws IOException { super(parent); this.subQueryWeight = subQueryWeight; + this.needsScores = needsScores; } @Override @@ -129,7 +137,7 @@ public Scorer scorer(LeafReaderContext context, Bits acceptDocs) throws IOExcept if (function != null) { leafFunction = function.getLeafScoreFunction(context); } - return new FunctionFactorScorer(this, subQueryScorer, leafFunction, maxBoost, combineFunction, minScore); + return new FunctionFactorScorer(this, subQueryScorer, leafFunction, maxBoost, combineFunction, minScore, needsScores); } @Override @@ -150,16 +158,21 @@ public Explanation explain(LeafReaderContext context, int doc) throws IOExceptio static class FunctionFactorScorer extends CustomBoostFactorScorer { private final LeafScoreFunction function; + private final boolean needsScores; - private FunctionFactorScorer(CustomBoostFactorWeight w, Scorer scorer, LeafScoreFunction function, float maxBoost, CombineFunction scoreCombiner, Float minScore) + private FunctionFactorScorer(CustomBoostFactorWeight w, Scorer scorer, LeafScoreFunction function, float maxBoost, CombineFunction scoreCombiner, Float minScore, boolean needsScores) throws IOException { super(w, scorer, maxBoost, scoreCombiner, minScore); this.function = function; + this.needsScores = needsScores; } @Override public float innerScore() throws IOException { - float score = scorer.score(); + // Even if the weight is created with needsScores=false, it might + // be costly to call score(), so we explicitly check if scores + // are needed + float score = needsScores ? scorer.score() : 0f; if (function == null) { return subQueryBoost * score; } else { diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/RandomScoreFunction.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/RandomScoreFunction.java index 934640e4ae040..a95f24eee91da 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/RandomScoreFunction.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/RandomScoreFunction.java @@ -81,4 +81,8 @@ public Explanation explainScore(int docId, Explanation subQueryScore) { }; } + @Override + public boolean needsScores() { + return false; + } } diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScoreFunction.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScoreFunction.java index babefd299e484..1f12336b2f7d1 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScoreFunction.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScoreFunction.java @@ -39,4 +39,11 @@ public CombineFunction getDefaultScoreCombiner() { } public abstract LeafScoreFunction getLeafScoreFunction(LeafReaderContext ctx) throws IOException; + + /** + * Indicates if document scores are needed by this function. + * + * @return {@code true} if scores are needed. + */ + public abstract boolean needsScores(); } diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java index d1ee646fc34d5..7d987165c861a 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/ScriptScoreFunction.java @@ -126,6 +126,13 @@ public Explanation explainScore(int docId, Explanation subQueryScore) throws IOE }; } + @Override + public boolean needsScores() { + // Scripts might use _score so we return true here + // TODO: Make scripts able to tell us whether they use scores + return true; + } + @Override public String toString() { return "script" + sScript.toString(); diff --git a/core/src/main/java/org/elasticsearch/common/lucene/search/function/WeightFactorFunction.java b/core/src/main/java/org/elasticsearch/common/lucene/search/function/WeightFactorFunction.java index db651ab8012b1..2def0712b1ace 100644 --- a/core/src/main/java/org/elasticsearch/common/lucene/search/function/WeightFactorFunction.java +++ b/core/src/main/java/org/elasticsearch/common/lucene/search/function/WeightFactorFunction.java @@ -71,6 +71,11 @@ public Explanation explainScore(int docId, Explanation subQueryScore) throws IOE }; } + @Override + public boolean needsScores() { + return false; + } + public Explanation explainWeight() { return Explanation.match(getWeight(), "weight"); } @@ -99,5 +104,10 @@ public Explanation explainScore(int docId, Explanation subQueryScore) { } }; } + + @Override + public boolean needsScores() { + return false; + } } } diff --git a/core/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionParser.java b/core/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionParser.java index b2c977e92bc42..3dc2427bca8e9 100644 --- a/core/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionParser.java +++ b/core/src/main/java/org/elasticsearch/index/query/functionscore/DecayFunctionParser.java @@ -289,6 +289,11 @@ public GeoFieldDataScoreFunction(GeoPoint origin, double scale, double decay, do this.fieldData = fieldData; } + @Override + public boolean needsScores() { + return false; + } + @Override protected NumericDoubleValues distance(LeafReaderContext context) { final MultiGeoPointValues geoPointValues = fieldData.load(context).getGeoPointValues(); @@ -352,6 +357,11 @@ public NumericFieldDataScoreFunction(double origin, double scale, double decay, this.origin = origin; } + @Override + public boolean needsScores() { + return false; + } + @Override protected NumericDoubleValues distance(LeafReaderContext context) { final SortedNumericDoubleValues doubleValues = fieldData.load(context).getDoubleValues();