Skip to content

Commit 74f37f0

Browse files
committed
Avoid scoring buffered unions when scores are ignored
BufferedUnionScorer can use score_doc during refill only when the score combiner needs scores. DoNothingCombiner now advertises that scoring is unnecessary, preserving the no-score path for count collectors and avoiding wasted score_doc calls. Add a regression test that verifies DoNothingCombiner does not invoke score() or score_doc() while counting a buffered union.
1 parent 72cca11 commit 74f37f0

4 files changed

Lines changed: 86 additions & 4 deletions

File tree

src/query/exclude.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,6 @@ where
111111
fn score(&mut self) -> Score {
112112
self.underlying_docset.score()
113113
}
114-
115114
}
116115

117116
#[cfg(test)]

src/query/score_combiner.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ pub trait ScoreCombiner: Default + Clone + Send + Copy + 'static {
5151
self.update(&mut scorer);
5252
}
5353

54+
/// Returns true if this combiner needs scorer scores to compute its state.
55+
fn requires_scoring() -> bool {
56+
true
57+
}
58+
5459
/// Clears the score combiner state back to its initial state.
5560
fn clear(&mut self);
5661

@@ -70,6 +75,10 @@ impl ScoreCombiner for DoNothingCombiner {
7075

7176
fn update_score(&mut self, _doc: DocId, _score: Score) {}
7277

78+
fn requires_scoring() -> bool {
79+
false
80+
}
81+
7382
fn clear(&mut self) {}
7483

7584
#[inline]

src/query/union/buffered_union.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,8 @@ impl<TScorer: Scorer, TScoreCombiner: ScoreCombiner> BufferedUnionScorer<TScorer
253253
score_combiner_fn: impl FnOnce() -> TScoreCombiner,
254254
num_docs: u32,
255255
) -> BufferedUnionScorer<TScorer, TScoreCombiner> {
256-
let use_score_doc_refill = docsets.iter().all(Scorer::can_score_doc);
256+
let use_score_doc_refill =
257+
TScoreCombiner::requires_scoring() && docsets.iter().all(Scorer::can_score_doc);
257258
let non_empty_docsets: Vec<TScorer> = docsets
258259
.into_iter()
259260
.filter(|docset| docset.doc() != TERMINATED)

src/query/union/mod.rs

Lines changed: 75 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ pub use simple_union::SimpleUnion;
1010
mod tests {
1111

1212
use std::collections::BTreeSet;
13+
use std::sync::atomic::{AtomicUsize, Ordering};
14+
use std::sync::Arc;
1315

1416
use common::BitSet;
1517

@@ -18,8 +20,8 @@ mod tests {
1820
use crate::postings::tests::test_skip_against_unoptimized;
1921
use crate::query::score_combiner::DoNothingCombiner;
2022
use crate::query::union::bitset_union::BitSetPostingUnion;
21-
use crate::query::{BitSetDocSet, ConstScorer, VecDocSet};
22-
use crate::{tests, DocId};
23+
use crate::query::{BitSetDocSet, ConstScorer, Scorer, VecDocSet};
24+
use crate::{tests, DocId, Score};
2325

2426
fn vec_doc_set_from_docs_list(
2527
docs_list: &[Vec<DocId>],
@@ -66,6 +68,61 @@ mod tests {
6668
}
6769
BitSetDocSet::from(doc_bitset)
6870
}
71+
72+
struct CountingScorer {
73+
docset: VecDocSet,
74+
score_calls: Arc<AtomicUsize>,
75+
score_doc_calls: Arc<AtomicUsize>,
76+
}
77+
78+
impl CountingScorer {
79+
fn new(
80+
doc_ids: Vec<DocId>,
81+
score_calls: Arc<AtomicUsize>,
82+
score_doc_calls: Arc<AtomicUsize>,
83+
) -> Self {
84+
CountingScorer {
85+
docset: VecDocSet::from(doc_ids),
86+
score_calls,
87+
score_doc_calls,
88+
}
89+
}
90+
}
91+
92+
impl DocSet for CountingScorer {
93+
fn advance(&mut self) -> DocId {
94+
self.docset.advance()
95+
}
96+
97+
fn seek(&mut self, target: DocId) -> DocId {
98+
self.docset.seek(target)
99+
}
100+
101+
fn doc(&self) -> DocId {
102+
self.docset.doc()
103+
}
104+
105+
fn size_hint(&self) -> u32 {
106+
self.docset.size_hint()
107+
}
108+
}
109+
110+
impl Scorer for CountingScorer {
111+
fn score(&mut self) -> Score {
112+
self.score_calls.fetch_add(1, Ordering::SeqCst);
113+
1.0
114+
}
115+
116+
fn can_score_doc(&self) -> bool {
117+
true
118+
}
119+
120+
fn score_doc(&mut self, _doc: DocId, _term_freq: u32) -> Score {
121+
self.score_doc_calls.fetch_add(1, Ordering::SeqCst);
122+
1.0
123+
}
124+
}
125+
69126
fn aux_test_union(docs_list: &[Vec<DocId>]) {
70127
for constructor in [
71128
posting_list_union_from_docs_list,
@@ -168,6 +225,22 @@ mod tests {
168225
]);
169226
}
170227

228+
#[test]
229+
fn test_do_nothing_combiner_does_not_score_buffered_docs() {
230+
let score_calls = Arc::new(AtomicUsize::new(0));
231+
let score_doc_calls = Arc::new(AtomicUsize::new(0));
232+
let scorers = vec![
233+
CountingScorer::new(vec![1, 3, 5], score_calls.clone(), score_doc_calls.clone()),
234+
CountingScorer::new(vec![2, 3, 6], score_calls.clone(), score_doc_calls.clone()),
235+
];
236+
237+
let mut union = BufferedUnionScorer::build(scorers, DoNothingCombiner::default, 10);
238+
239+
assert_eq!(union.count_including_deleted(), 5);
240+
assert_eq!(score_calls.load(Ordering::SeqCst), 0);
241+
assert_eq!(score_doc_calls.load(Ordering::SeqCst), 0);
242+
}
243+
171244
fn test_aux_union_skip(docs_list: &[Vec<DocId>], skip_targets: Vec<DocId>) {
172245
for constructor in [
173246
posting_list_union_from_docs_list,

0 commit comments

Comments
 (0)