Skip to content

perf: speed up _unescape_special_chars for large documents - #748

Open
SwastikTripathi wants to merge 1 commit into
trentm:masterfrom
SwastikTripathi:perf/unescape-special-chars
Open

SwastikTripathi wants to merge 1 commit into
trentm:masterfrom
SwastikTripathi:perf/unescape-special-chars

Conversation

@SwastikTripathi

@SwastikTripathi SwastikTripathi commented Oct 25, 2026 •

Copy link
Copy Markdown

Fixes #635.

Problem

_unescape_special_chars has two hidden O(n) costs that compound on large markdown documents:

  1. Full-string comparison for loop termination. Each pass through the while True loop stores orig_text = text and then compares text == orig_text to detect whether anything changed. On a large document this is an O(n) string comparison even when the very first regex scan found nothing to replace.

  2. Unnecessary intermediate allocations on every call. The replacements dict was built from three tuple(...) calls, converting _escape_table.items(), _code_table.items(), and a generator expression over html_blocks.items(), before iterating them into the dict. That's three extra list/tuple objects per call.

Fix

  • Early exit: if 'md5-' is not present anywhere in text, return immediately. This is the common case for plain paragraphs that contain no inline code, links, backslash escapes, or raw HTML, skipping both the dict build and the regex engine entirely.
  • Direct dict construction: iterate each source dict once, without allocating intermediate tuples.
  • Flag-based termination: a replaced flag (a one-element list so the inner function can mutate it) is set inside the re.sub callback whenever a match is found in the lookup table. The loop breaks when a full pass sets no flag, no full-string comparison needed.

All existing tests pass (test/test.py, test/api.doctests).

🤖 Generated with Claude Code

Two O(n) costs were paid per pass through the while loop:

1. The termination check compared the full text string (`text ==
   orig_text`), which is O(n) even when nothing changed.
2. The replacements dict was built from three intermediate tuples before
   every call (escape_table + code_table + reversed html_blocks).

Fix both:
- Skip immediately if 'md5-' is not present in the text (the common
  case for plain paragraphs with no inline code, links, or escapes).
- Build the replacements dict by iterating the source dicts directly,
  without allocating intermediate tuples.
- Replace the string comparison with a boolean flag set inside the
  re.sub callback; the loop breaks as soon as a full pass produces no
  replacements.

Fixes trentm#635

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

Bad performance in _unescape_special_chars

1 participant