Skip to content

perf: skip no-op quote/paren and zero-width passes (~10% on large text) - #72

Merged
yisding merged 1 commit into
mainfrom
claude/short-string-latency
Jun 14, 2026
Merged

yisding merged 1 commit into
mainfrom
claude/short-string-latency

Conversation

@yisding

@yisding yisding commented Jun 14, 2026

Copy link
Copy Markdown
Owner

Context

Follow-up to the competitive benchmarks (#71), which showed our short/medium segment() lags pySBD by ~22%. This investigated that — and the safe, behavior-preserving wins turned out to help large text, not short/medium (see CodSpeed result below).

What I found (honest)

Profiling a short (87-char) segment() shows the cost is dominated by the abbreviation pipeline (Aho-Corasick scan + per-abbreviation re.sub + the many apply_rules passes), which is core engine logic and risky to touch. The clearly-wasted work was smaller:

  • 8 between-punctuation regex passes run on every call even when the text has no quotes/parens.
  • A char-by-char zero-width rebuild runs (per output segment) even when there are no zero-width chars.

What this PR does

Two provably no-op-equivalent fast-paths:

  1. BetweenPunctuation — each base sub is anchored on its opening delimiter, so guard each method on opener in txt. Cuts 84 → 76 re.sub per short segment. The guard is per-method, not dispatcher-level — German repurposes sub_punctuation_between_double_quotes for „…“ (opener U+201E, no ASCII "), so a dispatcher '"' in txt check would skip that override and split inside the quote. (I hit exactly that bug mid-development; there's now a regression test that fails on the dispatcher form.)
  2. _strip_zero_width_before_sentence_closers — early-out via a precompiled search when no zero-width char is present.

CodSpeed result (the real verdict)

+10% on large text, short/medium untouched:

benchmark BASE HEAD
test_segment[large] 49.5 ms 44.9 ms +10.4%
test_should_wait_for_more[large] 50.7 ms 46 ms +10.2%

The removed work scales with segment count / text size (the zero-width guard runs per output segment), so it helps large text and is negligible on short. The short/medium gap vs pySBD is structural — the fixed-cost abbreviation pipeline — and is not addressed here; that needs a separate, deeper effort.

Correctness

  • Byte-identical output: full suite 1970 passed (+3 new), same 6 xfails.
  • New tests/regression/test_between_punctuation_guard.py covers the German „…“ trap, English quote/paren protection, and quote-free equivalence — verified to fail on the dispatcher-level form (AssertionError) and pass on the per-method form.

https://claude.ai/code/session_01RBqMKEGgvcZEAXLTPP5FiV

Two behavior-preserving fast-paths that cut per-call work on short prose:

- BetweenPunctuation: each quote/paren sub is anchored on its opening
  delimiter, so guard each base method on 'opener in txt' and return early
  when absent. On quote/paren-free text this turns 8 full-text regex
  passes into cheap membership checks (84 -> 76 re.sub per short
  segment()). The guard lives in each method, NOT the dispatcher, because
  language subclasses repurpose a base method for a different opener
  (German routes „…“ through sub_punctuation_between_double_quotes); a
  dispatcher-level check would skip that override and split inside the
  quote. Regression test covers exactly that German case.

- _strip_zero_width_before_sentence_closers: early-out via a precompiled
  search when the segment has no zero-width char, skipping the
  char-by-char rebuild on the common clean-text path.

Output is byte-identical (full suite 1970 passed). Wall-clock gain is
small (the skipped regex passes were already cheap no-match scans); the
win is in instruction count, which the competitive CodSpeed benchmarks
track against pySBD/punkt on short and medium input.
@yisding
yisding marked this pull request as ready for review June 14, 2026 01:59
@codspeed

codspeed Bot commented Jun 14, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 10.27%

⚡ 2 improved benchmarks
✅ 20 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
test_segment[large] 49.5 ms 44.9 ms +10.36%
test_should_wait_for_more[large] 50.7 ms 46 ms +10.17%

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/short-string-latency (8fc3ce2) with main (6b59901)

Open in CodSpeed

@yisding yisding changed the title perf: skip no-op quote/paren and zero-width passes on plain text perf: skip no-op quote/paren and zero-width passes (~10% on large text) Jun 14, 2026
@yisding
yisding merged commit 7da67c7 into main Jun 14, 2026
9 checks passed
@yisding
yisding deleted the claude/short-string-latency branch June 14, 2026 02:02
yisding pushed a commit that referenced this pull request Jun 14, 2026


Multi-agent + differential-profiling investigation of the short/medium
gap vs pySBD. Key finding: post-#72 there is no regression. Measured:
  - wall-clock: short 0.99x pysbd, medium 0.93x (parity/ahead)
  - instruction-count proxy (line-events): short 0.93x (we're ahead),
    down from 1.24x pre-#72

Root cause of the original ~28% CodSpeed gap: it was largely an
instruction-count artifact of pure-Python per-char loops (counted in full
by cachegrind, ~0 wall-clock), whose #1 contributor — the zero-width
scanner (876 of 2787 line-events) — was gated by #72. We also run FEWER
regex ops than pySBD (118 vs 266) and our Aho-Corasick is faster than a
naive 'in'-loop on short input, so neither pass-count nor discovery was
the cause.

Rewrites the plan as a 'extend the lead' menu, tiered by improves-both vs
CodSpeed-only: Tier 1 (list early-out/guards, resplit+callback gating,
always-on abbreviation gating), Tier 2 (Aho-Corasick DFA delta-table +
length-gated hybrid, ~2x scan), Tier 3 (glued-dot scanner C-regex,
instruction-count only). Adds benchmarks/differential_profile.py.
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.

2 participants