Skip to content

Commit 3ff4514

Browse files
committed
reduce number of allocations (#2257)
* reduce number of allocations Explanation makes up around 50% of all allocations (numbers not perf). It's created during serialization but not called. - Make Explanation optional in BM25 - Avoid allocations when using Explanation * use Cow
1 parent 01a792c commit 3ff4514

File tree

6 files changed

+58
-24
lines changed

6 files changed

+58
-24
lines changed

src/postings/serializer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ impl<W: Write> PostingsSerializer<W> {
355355
return;
356356
}
357357

358-
self.bm25_weight = Some(Bm25Weight::for_one_term(
358+
self.bm25_weight = Some(Bm25Weight::for_one_term_without_explain(
359359
term_doc_freq as u64,
360360
num_docs_in_segment,
361361
self.avg_fieldnorm,

src/query/bm25.rs

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ pub struct Bm25Params {
7777
/// A struct used for computing BM25 scores.
7878
#[derive(Clone)]
7979
pub struct Bm25Weight {
80-
idf_explain: Explanation,
80+
idf_explain: Option<Explanation>,
8181
weight: Score,
8282
cache: [Score; 256],
8383
average_fieldnorm: Score,
@@ -147,11 +147,30 @@ impl Bm25Weight {
147147
idf_explain.add_const("N, total number of docs", total_num_docs as Score);
148148
Bm25Weight::new(idf_explain, avg_fieldnorm)
149149
}
150+
/// Construct a [Bm25Weight] for a single term.
151+
/// This method does not carry the [Explanation] for the idf.
152+
pub fn for_one_term_without_explain(
153+
term_doc_freq: u64,
154+
total_num_docs: u64,
155+
avg_fieldnorm: Score,
156+
) -> Bm25Weight {
157+
let idf = idf(term_doc_freq, total_num_docs);
158+
Bm25Weight::new_without_explain(idf, avg_fieldnorm)
159+
}
150160

151161
pub(crate) fn new(idf_explain: Explanation, average_fieldnorm: Score) -> Bm25Weight {
152162
let weight = idf_explain.value() * (1.0 + K1);
153163
Bm25Weight {
154-
idf_explain,
164+
idf_explain: Some(idf_explain),
165+
weight,
166+
cache: compute_tf_cache(average_fieldnorm),
167+
average_fieldnorm,
168+
}
169+
}
170+
pub(crate) fn new_without_explain(idf: f32, average_fieldnorm: Score) -> Bm25Weight {
171+
let weight = idf * (1.0 + K1);
172+
Bm25Weight {
173+
idf_explain: None,
155174
weight,
156175
cache: compute_tf_cache(average_fieldnorm),
157176
average_fieldnorm,
@@ -202,7 +221,9 @@ impl Bm25Weight {
202221

203222
let mut explanation = Explanation::new("TermQuery, product of...", score);
204223
explanation.add_detail(Explanation::new("(K1+1)", K1 + 1.0));
205-
explanation.add_detail(self.idf_explain.clone());
224+
if let Some(idf_explain) = &self.idf_explain {
225+
explanation.add_detail(idf_explain.clone());
226+
}
206227
explanation.add_detail(tf_explanation);
207228
explanation
208229
}

src/query/boost_query.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ impl Weight for BoostWeight {
7474
fn explain(&self, reader: &SegmentReader, doc: u32) -> crate::Result<Explanation> {
7575
let underlying_explanation = self.weight.explain(reader, doc)?;
7676
let score = underlying_explanation.value() * self.boost;
77-
let mut explanation = Explanation::new(format!("Boost x{} of ...", self.boost), score);
77+
let mut explanation =
78+
Explanation::new_with_string(format!("Boost x{} of ...", self.boost), score);
7879
explanation.add_detail(underlying_explanation);
7980
Ok(explanation)
8081
}
@@ -151,7 +152,7 @@ mod tests {
151152
let explanation = query.explain(&searcher, DocAddress::new(0, 0u32)).unwrap();
152153
assert_eq!(
153154
explanation.to_pretty_json(),
154-
"{\n \"value\": 0.2,\n \"description\": \"Boost x0.2 of ...\",\n \"details\": [\n {\n \"value\": 1.0,\n \"description\": \"AllQuery\",\n \"context\": []\n }\n ],\n \"context\": []\n}"
155+
"{\n \"value\": 0.2,\n \"description\": \"Boost x0.2 of ...\",\n \"details\": [\n {\n \"value\": 1.0,\n \"description\": \"AllQuery\"\n }\n ]\n}"
155156
);
156157
Ok(())
157158
}

src/query/const_score_query.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,11 +164,9 @@ mod tests {
164164
"details": [
165165
{
166166
"value": 1.0,
167-
"description": "AllQuery",
168-
"context": []
167+
"description": "AllQuery"
169168
}
170-
],
171-
"context": []
169+
]
172170
}"#
173171
);
174172
Ok(())

src/query/explanation.rs

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::borrow::Cow;
12
use std::fmt;
23

34
use serde::Serialize;
@@ -16,12 +17,12 @@ pub(crate) fn does_not_match(doc: DocId) -> TantivyError {
1617
#[derive(Clone, Serialize)]
1718
pub struct Explanation {
1819
value: Score,
19-
description: String,
20-
#[serde(skip_serializing_if = "Vec::is_empty")]
21-
details: Vec<Explanation>,
22-
context: Vec<String>,
20+
description: Cow<'static, str>,
21+
#[serde(skip_serializing_if = "Option::is_none")]
22+
details: Option<Vec<Explanation>>,
23+
#[serde(skip_serializing_if = "Option::is_none")]
24+
context: Option<Vec<String>>,
2325
}
24-
2526
impl fmt::Debug for Explanation {
2627
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2728
write!(f, "Explanation({})", self.to_pretty_json())
@@ -30,12 +31,21 @@ impl fmt::Debug for Explanation {
3031

3132
impl Explanation {
3233
/// Creates a new explanation object.
33-
pub fn new<T: ToString>(description: T, value: Score) -> Explanation {
34+
pub fn new_with_string(description: String, value: Score) -> Explanation {
35+
Explanation {
36+
value,
37+
description: Cow::Owned(description),
38+
details: None,
39+
context: None,
40+
}
41+
}
42+
/// Creates a new explanation object.
43+
pub fn new(description: &'static str, value: Score) -> Explanation {
3444
Explanation {
3545
value,
36-
description: description.to_string(),
37-
details: vec![],
38-
context: vec![],
46+
description: Cow::Borrowed(description),
47+
details: None,
48+
context: None,
3949
}
4050
}
4151

@@ -48,17 +58,21 @@ impl Explanation {
4858
///
4959
/// Details are treated as child of the current node.
5060
pub fn add_detail(&mut self, child_explanation: Explanation) {
51-
self.details.push(child_explanation);
61+
self.details
62+
.get_or_insert_with(Vec::new)
63+
.push(child_explanation);
5264
}
5365

5466
/// Adds some extra context to the explanation.
5567
pub fn add_context(&mut self, context: String) {
56-
self.context.push(context);
68+
self.context.get_or_insert_with(Vec::new).push(context);
5769
}
5870

5971
/// Shortcut for `self.details.push(Explanation::new(name, value));`
60-
pub fn add_const<T: ToString>(&mut self, name: T, value: Score) {
61-
self.details.push(Explanation::new(name, value));
72+
pub fn add_const(&mut self, name: &'static str, value: Score) {
73+
self.details
74+
.get_or_insert_with(Vec::new)
75+
.push(Explanation::new(name, value));
6276
}
6377

6478
/// Returns an indented json representation of the explanation tree for debug usage.

src/query/term_query/term_query.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ impl TermQuery {
101101
..
102102
} => Bm25Weight::for_terms(statistics_provider, &[self.term.clone()])?,
103103
EnableScoring::Disabled { .. } => {
104-
Bm25Weight::new(Explanation::new("<no score>".to_string(), 1.0f32), 1.0f32)
104+
Bm25Weight::new(Explanation::new("<no score>", 1.0f32), 1.0f32)
105105
}
106106
};
107107
let scoring_enabled = enable_scoring.is_scoring_enabled();

0 commit comments

Comments
 (0)