Skip to content

Commit 3c91334

Browse files
authored
perf(search): latest tweaks to boost search performance (#42)
* perf(search): latest tweaks to boost search performance * chore(ci): testing codecov separation of python bindings * fix(ci): proposed fix for duplicate benchmark comment issue
1 parent 4a29a9b commit 3c91334

4 files changed

Lines changed: 54 additions & 17 deletions

File tree

.github/workflows/CI.yml

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,12 +104,32 @@ jobs:
104104
const fs = require('fs');
105105
const benchmarks = fs.readFileSync('BENCHMARKS.md', 'utf8');
106106
const body = `## Benchmark Results\n\n${benchmarks}`;
107-
github.rest.issues.createComment({
108-
issue_number: context.issue.number,
107+
const marker = '## Benchmark Results';
108+
const { data: comments } = await github.rest.issues.listComments({
109109
owner: context.repo.owner,
110110
repo: context.repo.repo,
111-
body: body
111+
issue_number: context.issue.number,
112112
});
113+
const existing = comments.find(
114+
(comment) =>
115+
comment.user?.login === 'github-actions[bot]' &&
116+
comment.body?.startsWith(marker)
117+
);
118+
if (existing) {
119+
await github.rest.issues.updateComment({
120+
owner: context.repo.owner,
121+
repo: context.repo.repo,
122+
comment_id: existing.id,
123+
body,
124+
});
125+
} else {
126+
await github.rest.issues.createComment({
127+
owner: context.repo.owner,
128+
repo: context.repo.repo,
129+
issue_number: context.issue.number,
130+
body,
131+
});
132+
}
113133
114134
test:
115135
name: Test on ${{ matrix.os }} / ${{ matrix.rust }}

.github/workflows/coverage.yml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,22 @@ jobs:
5050
# Run all tests, including the ignored python bindings
5151
cargo test --all-features -- --include-ignored
5252
# Generate the coverage report
53-
grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "tests/*" --ignore "examples/*" --ignore "build.rs" -o ./coverage.lcov
54-
grcov . --binary-path ./target/debug/ -s . -t cobertura --branch --ignore-not-existing --ignore "tests/*" --ignore "examples/*" --ignore "build.rs" -o ./coverage/cobertura.xml
53+
grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "tests/*" --ignore "examples/*" --ignore "build.rs" --ignore "src/lib.rs" -o ./coverage.lcov
54+
grcov . --binary-path ./target/debug/ -s . -t cobertura --branch --ignore-not-existing --ignore "tests/*" --ignore "examples/*" --ignore "build.rs" --ignore "src/lib.rs" -o ./coverage/cobertura.xml
55+
# Generate Python coverage report
56+
pip install uv
57+
uv venv
58+
source .venv/bin/activate
59+
uv pip install maturin pytest coverage
60+
rm -rf target/wheels
61+
maturin build --release
62+
uv pip install target/wheels/*.whl
63+
coverage run -m pytest tests/python/ -vv
64+
coverage xml -o coverage/python-coverage.xml
5565
5666
- name: Upload coverage to Codecov
5767
uses: codecov/codecov-action@v5
5868
with:
5969
token: ${{ secrets.CODECOV_TOKEN }}
60-
files: ./coverage/cobertura.xml
70+
files: ./coverage/cobertura.xml,./coverage/python-coverage.xml
6171
fail_ci_if_error: true

src/python/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ use pyo3::create_exception;
88
use pyo3::prelude::*;
99
use std::sync::Arc;
1010

11-
// TODO: Lazily lumped all errors here but maybe more specific errors can done? like
12-
// FeatureExtractionError and the likes
1311
create_exception!(simstring_rust, SearchError, pyo3::exceptions::PyValueError);
1412

1513
// Wrapper for FeatureExtractor trait as I can't find any direct translation.
@@ -246,7 +244,6 @@ impl PySearcher {
246244
SearchError::new_err(format!("Invalid threshold: {val}"))
247245
}
248246
})?;
249-
// TODO: Explore if the python bindings can handle returning references
250247
Ok(results.into_iter().map(|s| s.to_string()).collect())
251248
}
252249

src/search.rs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,20 +104,25 @@ impl<'db, M: Measure> Searcher<'db, M> {
104104
let min_feat_size = self.measure.min_feature_size(query_size, alpha);
105105
let max_feat_size = self.measure.max_feature_size(query_size, alpha, self.db);
106106

107-
let mut all_candidates: Vec<StringId> = (min_feat_size..=max_feat_size)
107+
(min_feat_size..=max_feat_size)
108108
.into_par_iter()
109-
.flat_map(|candidate_size| {
109+
.map(|candidate_size| {
110110
let tau =
111111
self.measure
112112
.minimum_common_feature_count(query_size, candidate_size, alpha);
113-
self.overlap_join(query_features, tau, candidate_size)
114-
})
115-
.collect();
116113

117-
all_candidates.sort_unstable();
118-
all_candidates.dedup();
114+
if tau == 0 || tau > query_size {
115+
return FxHashSet::default();
116+
}
119117

120-
all_candidates.into_iter().collect()
118+
self.overlap_join(query_features, tau, candidate_size)
119+
.into_iter()
120+
.collect::<FxHashSet<StringId>>()
121+
})
122+
.reduce(FxHashSet::default, |mut acc, set| {
123+
acc.extend(set);
124+
acc
125+
})
121126
}
122127

123128
fn overlap_join(
@@ -136,6 +141,11 @@ impl<'db, M: Measure> Searcher<'db, M> {
136141
.map(|&feature| self.db.lookup_strings(candidate_size, feature))
137142
.collect();
138143

144+
let available_features = feature_sets.iter().filter(|set| set.is_some()).count();
145+
if available_features < tau {
146+
return Vec::new();
147+
}
148+
139149
let mut feature_indices: Vec<usize> = (0..query_features.len()).collect();
140150
feature_indices.sort_unstable_by_key(|&i| feature_sets[i].map_or(usize::MAX, |s| s.len()));
141151

0 commit comments

Comments
 (0)