Repository navigation
perf(docx): memoize python-docx style lookups - #4862
Conversation
python-docx resolves paragraph.style and run.style through get_style(), which scans the whole styles part on every call, several times per paragraph and run. Styles do not change during conversion, so memoize the lookup on the loaded part: a 135-page DOCX converts in 2.6 s instead of 6.1 s. Signed-off-by: wenqiw777 <wenqiw@umich.edu>
|
✅ DCO Check Passed Thanks @wenqiw777, all your commits are properly signed off. 🎉 |
Merge Protections🟢 Merge protection satisfied — ready to merge. Show 1 satisfied protection🟢 Enforce conventional commitMake sure that we follow https://www.conventionalcommits.org/en/v1.0.0/
|
AI triage (advisory)Checked commit Warning The AI analysis did not finish. Only the deterministic checks are shown. Topic labels added: CI safety: not assessed. Possibly related pull requests
The |
There was a problem hiding this comment.
AI first review (advisory): no blocker found
This review covers the full diff at 4d9bf8fbef. It read the code and did not run it.
The patch attaches functools.cache to a per-load DocumentPart instance, matching the accepted ODF pattern. The cache is scoped to each loaded document, so no cross-document leakage is possible. The regression test constructs two documents with different Normal-style bold settings and converts them in sequence; it is sufficient to catch a class-level cache mistake. No behavioral regression or contract change was found.
A maintainer makes the merge decision. This review never approves the pull request.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
|
I could have done the same changes for docx, but I was focused on our own usage |
python-docx resolves
paragraph.styleandrun.stylethroughDocumentPart.get_style(), which scans the whole styles part on every call: an XPath per style id, and a full scan with enum parsing of every style for the default style. The DOCX backend reads the paragraph style several times per paragraph and once per run, so a 135-page document makes ~35k lookups for 12 distinct styles. Styles do not change during conversion, so the lookup is memoized on the loaded document part, as #4763 does for the ODF backend.Checklist:
Review evidence:
Generated DOCX reports (headings, paragraphs with bold/italic runs, nested bullet and numbered lists, tables, header and footer), 5 warm runs each, Python 3.12, python-docx 1.2.0, Apple Silicon:
Peak RSS is unchanged (447 / 500 / 602 MB before, 446 / 501 / 608 MB after). In cProfile of the 135-page file,
CT_Styles.default_forandget_by_idtook 5.2 s of 10.4 s profiled time; with the memo, the 34,793get_stylecalls hit 12 cache entries.Outputs are identical: all 97 fixtures under
tests/data/{docx,html,md,xlsx,pptx}/sourceswere converted withmainand with this branch and exported to JSON and Markdown;diff -rreports no differences.pytest tests/test_backend_msword*.pypasses (292 passed, 1 skipped, including the new test). No reference data change.The new test converts two documents whose default paragraph style differs in one process and checks that each keeps its own bold, so the memo cannot leak across documents.