Skip to content

perf(docx): memoize python-docx style lookups - #4862

Merged
dolfim-ibm merged 1 commit into
docling-project:mainfrom
wenqiw777:perf/docx-style-cache
Oct 19, 2026
Merged

dolfim-ibm merged 1 commit into
docling-project:mainfrom
wenqiw777:perf/docx-style-cache

Conversation

@wenqiw777

Copy link
Copy Markdown
Contributor

python-docx resolves paragraph.style and run.style through DocumentPart.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:

  • Documentation has been updated, if necessary.
  • Examples have been added, if necessary.
  • Tests have been added, if necessary.

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:

document before (median / min) after (median / min) speedup
135 pages, 5.9k items 6.13 s / 5.92 s 2.61 s / 2.50 s 2.3x
270 pages, 11.8k items 11.87 s / 11.84 s 5.26 s / 5.22 s 2.3x
535 pages, 23.5k items 24.03 s / 24.01 s 10.61 s / 10.53 s 2.3x

Peak RSS is unchanged (447 / 500 / 602 MB before, 446 / 501 / 608 MB after). In cProfile of the 135-page file, CT_Styles.default_for and get_by_id took 5.2 s of 10.4 s profiled time; with the memo, the 34,793 get_style calls hit 12 cache entries.

Outputs are identical: all 97 fixtures under tests/data/{docx,html,md,xlsx,pptx}/sources were converted with main and with this branch and exported to JSON and Markdown; diff -r reports no differences. pytest tests/test_backend_msword*.py passes (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.

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>
@github-actions

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @wenqiw777, all your commits are properly signed off. 🎉

@mergify

mergify Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Merge Protections

🟢 Merge protection satisfied — ready to merge.

Show 1 satisfied protection

🟢 Enforce conventional commit

Make sure that we follow https://www.conventionalcommits.org/en/v1.0.0/

  • title ~= ^(fix|feat|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\(.+\))?(!)?:

@github-actions github-actions Bot added docx issue related to docx backend performance labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

AI triage (advisory)

Checked commit 4d9bf8fbef. The analysis read the diff only. It did not run code from this pull request.

Warning

The AI analysis did not finish. Only the deterministic checks are shown.

Topic labels added: performance, docx.

CI safety: not assessed.

Possibly related pull requests

PR State Signal Assessment
#4831 open changes the same code not assessed
#4584 open changes the same code not assessed
#4763 merged referenced as #4763 not assessed

The ai:* labels are advisory. They do not approve CI and they do not add tests:full. A new push runs this check again.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions github-actions Bot added the ai:review-lgtm AI first review found no blocker or major issue (advisory) label Oct 16, 2026
@codecov

codecov Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread docling/backend/msword_backend.py
Comment thread docling/backend/msword_backend.py
@ExtReMLapin

Copy link
Copy Markdown
Contributor

I could have done the same changes for docx, but I was focused on our own usage

@PeterStaar-IBM PeterStaar-IBM left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

@dolfim-ibm
dolfim-ibm merged commit 25a644d into docling-project:main Oct 19, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai:review-lgtm AI first review found no blocker or major issue (advisory) docx issue related to docx backend performance

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants