Optimize jieba hot paths - #152
Merged
Merged
Conversation
Generate one phf::Map<char, [f64; 4]> for builtin HMM emission probabilities and store runtime HmmModel emissions as char -> [f64; 4]. Viterbi now does one emission lookup per input character instead of one lookup per state per character, and runtime models avoid UTF-8 re-encoding on lookup. Measured with: cargo build --release -p weicheng && hyperfine --warmup 3 --runs 10 './target/release/weicheng'. Result: 732.8ms mean baseline -> 663.5ms mean after this change, a 9.5% improvement. Verification: cargo test -p jieba-rs --all-features passed.
Store log_total on Jieba and refresh it whenever total changes through load_dict, add_word, or clear. calc and suggest_freq now reuse the cached logarithm instead of recomputing (total as f64).ln() for each block. Measured with the ./target/release/weicheng internal timer over 10 runs after the HMM emission commit. Result: median 565ms -> 562ms, a 0.5% improvement. Verification: cargo test -p jieba-rs --all-features passed.
Hoist the StaticSparseDAG allocation out of cut_all_tokens and keep one reusable DAG in cut_all_toplevel, clearing it after each matched block. This avoids allocating fresh sparse DAG storage for every CJK block in cut_all. Measured with: cargo bench -p jieba-rs --features tfidf,textrank -- cut/cut_all. Result: 722.05ns baseline -> 633.44ns after this change, a 12.5% improvement. Verification: cargo test -p jieba-rs --all-features passed.
Replace the iterator map/max_by chain in calc with a direct loop that tracks the best probability and preserves the existing tie-break toward the larger byte_end. This avoids iterator adapter overhead in the route dynamic-programming hot path. Measured with: cargo bench -p jieba-rs --features tfidf,textrank -- cut/. Results: cut/no_hmm 646.83ns -> 592.07ns (8.2% faster), cut/with_hmm 868.17ns -> 778.18ns (10.4% faster), and cut/cut_for_search 1.0472us -> 983.70ns (6.1% faster). Verification: cargo test -p jieba-rs --all-features passed.
Build a candidate_ids table once per TextRank extraction, mapping each tag position to its eligible word id. The co-occurrence loop now uses two array reads per pair instead of repeatedly checking allowed POS, allocating in is_keyword, and looking up word ids inside the span loop. Measured with: cargo bench -p jieba-rs --features tfidf,textrank -- keywords/textrank. Result: 4.7337us baseline -> 3.6723us after this change, a 22.1% improvement. Verification: cargo test -p jieba-rs --all-features passed.
Keep the public stop_words BTreeSet API, but build an internal FxHashSet lookup table in KeywordExtractConfig. Keyword filtering now uses the hash table for stop-word membership while preserving the existing builder and accessor types. Measured with: cargo bench -p jieba-rs --features tfidf,textrank -- keywords/. Results: keywords/tfidf 2.0706us -> 1.7540us (15.3% faster) and keywords/textrank 3.6809us -> 3.3084us (10.1% faster). Verification: cargo test -p jieba-rs --all-features passed.
Scan each candidate keyword once to count characters and detect uppercase characters, check the stop-word table directly, and only allocate a lowercase string when uppercase text needs case-folded lookup. This removes the unconditional to_lowercase allocation from keyword filtering. Measured with: cargo bench -p jieba-rs --features tfidf,textrank -- keywords/. Results: keywords/tfidf 1.7540us -> 1.4881us (15.2% faster) and keywords/textrank 3.3084us -> 3.0799us (6.9% faster). Verification: cargo test -p jieba-rs --all-features passed.
Route load_default_dict through a private unique-dictionary loader when the Jieba instance is empty. The bundled default dictionary has unique word keys, so initial construction can avoid an exact_match_search before every cedar update while public load_dict and non-empty load_default_dict calls keep the duplicate/update semantics. Measured with: cargo bench -p jieba-rs --features tfidf,textrank -- jieba/new. Result: 55.691ms baseline -> 51.341ms after this change, a 7.8% improvement. Verification: cargo test -p jieba-rs --all-features passed.
Address clippy suggestions in POS tagging helpers after dropping the posseg scratch-buffer optimization. This keeps the validation command warning-free without changing the public API or claiming a performance optimization.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #152 +/- ##
==========================================
+ Coverage 83.10% 83.37% +0.26%
==========================================
Files 10 10
Lines 2001 2051 +50
==========================================
+ Hits 1663 1710 +47
- Misses 338 341 +3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
messense
marked this pull request as ready for review
July 6, 2026 14:26
Merging this PR will improve performance by 14.47%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | single_thread |
10.5 ms | 8.6 ms | +22.57% |
| ⚡ | multi_thread |
10.5 ms | 8.6 ms | +22.05% |
| ⚡ | textrank |
67.1 µs | 56.1 µs | +19.66% |
| ⚡ | tfidf |
43.4 µs | 38.7 µs | +11.94% |
| ⚡ | search_mode |
41.5 µs | 37.3 µs | +11.43% |
| ⚡ | with_hmm |
34.4 µs | 30.9 µs | +11.14% |
| ⚡ | cut_all |
23.9 µs | 21.5 µs | +11.11% |
| ⚡ | default_mode |
34.4 µs | 31 µs | +11.03% |
| ⚡ | tag |
37.1 µs | 33.6 µs | +10.21% |
Tip
Curious why this is faster? Comment @codspeedbot explain why this is faster on this PR, or directly use the CodSpeed MCP with your agent.
Comparing codex/perf-optimizations (21e04f8) with main (1e77e50)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR applies more performance optimizations that measured as wins and leaves out the candidates that either regressed or carried feature-parity risk.
Kept changes:
Jieba::log_totaland refresh it when dictionary totals change.cut_alland replace the routemap/max_bychain with an explicit max loop.BTreeSetAPI.Not included:
cut_for_searchprefix prototype and route-buffer reuse prototype, because they did not improve the measured workloads.Benchmarks
Measured locally against
mainat1e77e50. Criterion values are median estimates fromcargo bench -p jieba-rs --features tfidf,textrank; thetag_with_oovrow uses the corrected focused rerun after restoring the faster indexed POS loop.mainjieba/newcut/no_hmmcut/with_hmmcut/cut_allcut/cut_for_searchtokenize/default_modetokenize/search_modejieba/tagjieba/tag_with_oovkeywords/tfidfkeywords/textrankmultithreaded/single_threadmultithreaded/multi_threadAcross these Criterion rows, the unweighted average speedup is about 17.3% and the median speedup is 17.2%.
The
weichengrelease binary improved from a 10-run median of 667.5 ms onmainto 593 ms on this branch, about 11.2% faster, using:Validation
cargo fmt --all --check cargo test -p jieba-rs --all-features cargo clippy --all-targets --all-features -- -D warnings