Repository navigation
👌 Speed up rendering on PyPy by joining fragments once - #458
Open
DicksonWu654 wants to merge 3 commits into
Open
DicksonWu654 wants to merge 3 commits into
DicksonWu654 wants to merge 3 commits into
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Retain the initial empty operand so buffered rendering preserves string identity visible to custom reflected addition and single-fragment callers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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
RendererHTML.render()andrenderInline()build their output withresult += fragment. CPython extends a local string in place, but PyPy does not, so each append copies the whole rendered prefix. For a large document this dominates rendering time on PyPy.On PyPy only, both methods now collect fragments in a list and join once. CPython keeps exactly the current code: the implementation is chosen once at class creation, and the CPython branch is the existing method body, re-indented (its bytecode is unchanged on 3.12 and 3.14).
Behaviour kept for custom render rules:
str(astrsubclass with its own__add__/__radd__, or an invalid object), the prefix is materialised at that point and rendering continues with+=. Overloaded addition, the returned type, rule call order and the first raised error stay as before."", so a render that produces a single fragment still returns a new object, as string addition does on PyPy. A custom reflected addition can observe that identity.The second commit is that identity fix, so the two commits belong together.
Performance
Whole-document timings. Each pair runs master and this branch in fresh processes, pinned to one core, alternating AB/BA order. Times are warm: 25 warmup rounds on PyPy and 2 on CPython, then 10 or 6 timed rounds. Ratios are master time / branch time (median of per-pair ratios, with min-max over pairs). Documents:
benchmarking/samples/spec.md(CommonMark spec), and the repository docs plus README and CHANGELOG concatenated.render(spec.md), commonmarkrender(docs), gfm-likeparse(spec.md))On CPython there is no change, as expected: an A/A run on the same machine (master against master) spreads just as widely, with medians between 0.93x and 1.01x.
Validation
tests/test_api/test_renderer_results.pycovers custom rules returningstrsubclasses with__add__/__radd__, non-string results and the error they raise, rule call order and prefix identity. These tests also pass on master (CPython and PyPy), so they pin the existing behaviour.Token.as_dict()streams and env keys, over the CommonMark spec examples, every test fixture (whole files and individual cases), the repository docs, and Markdown files and package metadata from an installed environment, for 3,528 documents in all. It runs them under thecommonmark,default,zero,gfm-like,gfm-like2andjs-defaultpresets,gfm-likewith linkify and typographer, andcommonmarkwith mdit-py-plugins (front matter, footnote, deflist, tasklists, dollarmath, attrs, container). All 28,224 records are identical.pre-commit run --all-filespasses (ruff, ruff-format, mypy).🤖 Generated with Claude Code