Repository navigation
Fix quadratic time for unclosed parentheses in link targets - #1680
Open
ParamTanna wants to merge 1 commit into
Open
ParamTanna wants to merge 1 commit into
ParamTanna wants to merge 1 commit into
Conversation
`LinkInlineProcessor.getLink` scans forward from the `(` of a link target
to pair the brackets. The treeprocessor calls it for every `[text](` in
the text, and when the parentheses never close each call scans to the end,
so conversion time grows with the square of the number of unclosed `(`.
For example `markdown.markdown('[a](' * n)` on CPython 3.12:
'[a](' x4000 (16000 bytes): 2.27s -> 0.015s
'[a](x ' x4000 (24000 bytes): 3.56s -> 0.018s
'[a](<u>' x4000 (28000 bytes): 4.11s -> 0.064s
When no quote appears before the matching `)` the scan is a plain bracket
match, so resolve it from a table of closing positions built in a single
pass per text and only fall back to the character-by-character loop when a
quote may start a title (the quote/title inputs were already linear). The
new `getLink` was compared with the old loop at every index of all 1.1M
strings up to length 6 over the 10 characters `[ ] ( ) ' " < > a` and
space, and of 2M longer random strings; the returned tuples were identical
in every case.
This is the `getLink` companion to the `getText` fix in Python-Markdown#1664.
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.
Description
LinkInlineProcessor.getLinkscans forward from the(of a link target to pair the brackets. The treeprocessor calls it for every[text](in the text, and when the parentheses never close each call scans to the end, so conversion time grows with the square of the number of unclosed(. Timings formarkdown.markdown(s * n)on CPython 3.12:This is the
getLinkcompanion to #1664 (which fixed the same problem ingetTextand noted thatgetLinkwas left out because its quote/title handling is more involved).The quadratic inputs all turn out to be the ones with no quote before the matching
)— there the scan is just a plain bracket match. Those are now resolved from a table of closing positions built in a single pass per text (_findParenEnds, same monotonic-stack idea as #1664'sgetText), guarded by a per-text "next quote" table so the fast path only fires when no quote can start a title. Inputs where a quote may begin a title fall back to the original character-by-character loop unchanged; I measured those and they were already linear, so nothing is lost.To be sure the fast path is behaviour-preserving, I compared the new
getLinkagainst the old loop on the raw(href, title, index, handled)tuple at every index of:[]()'"<>aand space, andThe results were identical in every case. The existing test suite passes unchanged (1166 tests), plus two new regression tests.
test_many_unclosed_link_parenstakes several seconds without the fix and finishes instantly with it.AI Assistance Disclosure
No AI tools were used in preparing this PR.
AI tools were used and I have disclosed which ones, and have fully reviewed and verified their output.
AI tools were used to assist with the following:
Tool used: Claude Code (Claude Opus 4.8). I reviewed and verified all of it before submitting, including running the equivalence comparison and the full test suite myself.
Checklist