perf: cut segmentation + streaming latency (quote-resplit + single-pass streaming) - #69
Merged
Merged
Conversation
yisding
marked this pull request as ready for review
June 14, 2026 00:41
Merging this PR will improve performance by 54.38%
|
| Benchmark | BASE |
HEAD |
Efficiency | |
|---|---|---|---|---|
| ⚡ | test_stream_feed_document[conservative] |
57.2 ms | 26.9 ms | ×2.1 |
| ⚡ | test_stream_feed_document[aggressive] |
48.8 ms | 23.3 ms | ×2.1 |
| ⚡ | test_segment_multilingual[ru] |
2.3 ms | 1.5 ms | +47.16% |
| ⚡ | test_should_wait_for_more[large] |
73.4 ms | 50.6 ms | +44.93% |
| ⚡ | test_segment[large] |
71.6 ms | 49.5 ms | +44.66% |
| ⚡ | test_should_wait_for_more[short] |
5.3 ms | 3.7 ms | +41.94% |
| ⚡ | test_should_wait_for_more[medium] |
5.6 ms | 4 ms | +39.76% |
| ⚡ | test_segment[medium] |
4 ms | 2.9 ms | +38.92% |
| ⚡ | test_segment[short] |
1.8 ms | 1.4 ms | +31.88% |
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 claude/library-performance-avenues-pnykwq (c801546) with main (772fda5)
yisding
force-pushed
the
claude/library-performance-avenues-pnykwq
branch
from
June 14, 2026 00:54
0ea4efa to
75fcebe
Compare
Adds a focused wall-clock latency harness covering the three latency-sensitive paths (one-shot segment(), should_wait_for_more() lookahead, and StreamSegmenter.feed()) plus a cProfile hot-function breakdown. Used to establish a performance baseline.
_resplit_segments computed the abbreviation-protected scan (replace_abbreviations) eagerly for every postprocessed sentence, even though _resplit_multi_sentence_quote discards it immediately unless the segment begins with a leading quote. For a 7-sentence quote-free string that meant 8 full abbreviation passes (1 legitimate main-pipeline pass + 7 wasted per-sentence re-scans), the single largest contributor to segment() latency. Gate the protected-scan computation behind the same leading-quote check the resplit uses, via a new _maybe_resplit_multi_sentence_quote helper. Behavior is identical (the scan is only consulted after that gate), but quote-free segments now skip it entirely. Measured: ~30-40% lower segment()/should_wait_for_more() latency and ~23% lower per-token streaming cost; full suite unchanged (1938 -> 1939 with the added regression test guarding the call count).
…pass StreamSegmenter._detect ran two full segmentations of the unemitted buffer on every feed(): segment_spans() for the tail spans, then should_wait_for_more() — whose internal _segment_result re-ran the same process() + _match_spans mapping over the identical buffer purely to recover the trailing-boundary verdict. Add Segmenter.segment_spans_with_lookahead(), which derives both the spans and the should_wait verdict from a single process()/_match_spans pass (the spans' .sent values are exactly the comparison segments _wait_for_last_segment consumes), and have _detect use it. The boundary, punctuation, and lookahead logic are byte-for-byte unchanged across all supported languages — verified identical StreamSegmenter output vs. the prior implementation over 9 languages x 3 buffering modes x 3 feed granularities, with span round-trips still exact. Measured: ~35% lower per-token streaming latency (conservative 0.44 -> 0.29 ms/token; aggressive 0.39 -> 0.25 ms/token). Tail-probe cost for period-terminated boundaries is unchanged and still only runs for '.'. Adds per-language equivalence tests for the combined API and a guard that _detect maps the buffer exactly once per delta.
Hoist the remaining constant-pattern raw re.sub/re.search calls in the hot path (AbbreviationReplacer.replace and its a.m./p.m. and initialism rules, ListItemReplacer line-break guards and roman-numeral parens, ExclamationWords.apply_rules) and a few per-language constants (Slovak ordinal/roman/quote rules, the Russian conjunction-continuation check) to module/class-level compiled patterns, matching the existing _ALLCAPS_IMPRINT_RE convention. These patterns were recompiled via a string-literal each call. In practice CPython's internal re cache already memoizes them, so this is not a measurable speedup on the warm benchmark; the value is removing the latent re-cache-eviction cliff under many-pattern / multi-language workloads and matching the precompiled style used elsewhere. Behavior is unchanged: full suite still 1967 passed. Patterns that interpolate per-abbreviation variables (escaped/am_escaped/month) are genuinely dynamic and intentionally left as-is.
yisding
force-pushed
the
claude/library-performance-avenues-pnykwq
branch
from
June 14, 2026 01:08
75fcebe to
c801546
Compare
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.
What
Latency-profiling harness plus two safe, behavior-preserving performance fixes it surfaced. No boundary/punctuation/language logic changes — all paths verified byte-identical to before.
1.
benchmarks/latency_baseline.pyLocal wall-clock latency harness for the three latency-sensitive paths (
segment(),should_wait_for_more(),StreamSegmenter.feed()) + a cProfile hot-function breakdown.2.
perf:skip abbreviation re-scan for quote-free segments_resplit_segmentsevaluatedprotected_text=self.replace_abbreviations(...)eagerly for every sentence, though_resplit_multi_sentence_quotediscards it unless the segment begins with a leading quote — so a 7-sentence quote-free string ran 8 abbreviation passes (1 needed + 7 wasted; ~58% ofsegment()time). Gated behind the same leading-quote check via_maybe_resplit_multi_sentence_quote.3.
perf:segment the streaming buffer once per deltaStreamSegmenter._detectran two full segmentations of the buffer on everyfeed(). NewSegmenter.segment_spans_with_lookahead()derives both the spans and the trailing-boundary verdict from one pass;_detectuses it.4.
refactor:precompile constant regexesHoisted the remaining constant-pattern raw
re.sub/re.searchcalls in the hot path to compiled module/class constants. Within-noise on the warm benchmark (CPython'srecache already memoizes them) — kept for cache-eviction robustness + convention consistency.Measured impact (Python 3.13, warm caches, local)
segment()short (87c)segment()medium (198c)segment()large (3979c)should_wait_for_more()mediumStreamSegmenter.feed()conservativeStreamSegmenter.feed()aggressiveCorrectness / tests
segment_spans_with_lookahead==segment_spans+should_wait_for_morefor everyLANGUAGE_CODE.8 == 1;2 == 1).https://claude.ai/code/session_01RBqMKEGgvcZEAXLTPP5FiV