Skip to content

perf: speed up string_matcher hot path and fix index-list pruning#28

Open
Garulf wants to merge 1 commit into
mainfrom
perf/string-matcher
Open

perf: speed up string_matcher hot path and fix index-list pruning#28
Garulf wants to merge 1 commit into
mainfrom
perf/string-matcher

Conversation

@Garulf

@Garulf Garulf commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

Efficiency pass over pyflowlauncher/string_matcher.py keeping the algorithm's shape, plus two bug fixes found along the way. The matcher runs once per candidate result per keystroke, so per-character costs dominate.

Speedups

  • all_previous_chars_matched now uses a single slice comparison (C-level, short-circuiting) instead of a per-char Python loop that kept comparing after the first mismatch
  • Acronym char/number flags are computed once per character; previously is_acronym + is_acronym_count re-evaluated isupper()/isdigit()/prev-space up to twice each per character. is_acronym_char also checks cheap positional conditions before isupper()
  • Main loop binds the current character via enumerate and hoists len() calls; removed the dead X or (X and Y) half of the break condition
  • Inlined the one-comparison all_query_substrings_matched_func wrapper
  • calculate_closest_space_index uses bisect_left (space_indices is built sorted)

Bug fixes

  • get_updated_index_list popped from the list while iterating it, skipping the element after each removal and leaving stale highlight indices. Now a comprehension, matching the upstream C# RemoveAll(x => x >= firstMatchedIndexInWord) semantics
  • Queries with consecutive spaces produced empty substrings via split(' ') and raised IndexError; split() fixes this

Verification

  • Captured before/after behavior on a 335,068-case corpus (curated + fuzzed): match results and scores are identical everywhere; the only diffs are the two fixed bugs (156 former IndexError cases now return no-match, 4 cases lose the stale highlight indices)
  • ~1.7x faster on a mixed workload (12.5s → 7.2s per 100k uncached calls)
  • tox -e lint,type,py38,py312 all green (136 tests), with two new regression tests

- Compare partial word matches with a C-level slice comparison instead
  of a per-char loop that never short-circuited
- Compute acronym char/number flags once per character instead of up to
  twice each via nested helper calls; reorder is_acronym_char so the
  cheap positional checks run before isupper()
- Bind the current character via enumerate and hoist len() calls out of
  the per-character loop; drop the dead second clause of the break
  condition
- Inline the trivial all_query_substrings_matched_func wrapper
- Use bisect for calculate_closest_space_index (space_indices is sorted)
- Fix pop-while-iterating in get_updated_index_list that skipped
  elements, leaving stale highlight indices; now matches upstream
  RemoveAll semantics
- Split the query with split() so consecutive spaces no longer produce
  empty substrings that raise IndexError

Verified against a 335k-case corpus: scores and match results are
identical; only the two fixed bugs change behavior. ~1.7x faster on a
mixed workload.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant