Skip to content

perf(docx): cache style chains and reuse paragraph content (1.6x faster) - #4867

Merged
dolfim-ibm merged 1 commit into
docling-project:mainfrom
deadstrobe5:docx-style-chain-cache
Oct 19, 2026
Merged

dolfim-ibm merged 1 commit into
docling-project:mainfrom
deadstrobe5:docx-style-chain-cache

Conversation

@deadstrobe5

Copy link
Copy Markdown
Contributor

Converting a DOCX spends most of its time in python-docx lookups that repeat for every run. Two of them are fixed here.

Every base_style hop in the style chain is an XPath query over the styles part, and the bold check walks the chain for every run. The chain is now resolved once per style and cached on the backend.

_handle_text_elements read a paragraph's content three times: once in _get_paragraph_text and twice in _get_paragraph_elements (the emptiness check and the grouping). Each read resolves the formatting of every run. The last paragraph's content is now remembered, so it's computed once. Both helper methods keep their signatures, so the existing tests that patch them still work.

This doesn't overlap #4862, which memoizes get_style; with both applied the gains stack.

Checklist:

  • Documentation has been updated, if necessary. (not needed)
  • Examples have been added, if necessary. (not needed)
  • Tests have been added, if necessary. (behaviour unchanged, covered by existing tests)

Review evidence:

Median of 5 fresh processes per side, Python 3.13, Apple M5:

input main this PR speedup
generated report, 500 sections 3.30 s 2.04 s 1.61x
generated mixed styles, links, code, lists 1.38 s 0.83 s 1.67x
500 sections, on top of #4862 1.26 s 0.65 s 1.94x

export_to_markdown() and export_to_dict() are byte-identical before and after for all 36 files in tests/data/docx/sources and the generated documents, so no reference data changes. tests/test_backend_msword*.py: 290 passed; test_e2e_docx_conversions and test_chart_image_rendering fail the same way on main in my environment (drawingml chart ground truth). ruff check and ruff format are clean.

Signed-off-by: Afonso Ribeiro <afonso.diamantino2@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

✅ DCO Check Passed

Thanks @deadstrobe5, 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 ai:ci-safe AI triage found no CI-safety concern (advisory) docx issue related to docx backend performance labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

AI triage (advisory)

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

This PR caches the basedOn style chain per style element and avoids re-resolving paragraph content on each call to _get_paragraph_text and _get_paragraph_elements, making DOCX conversion roughly 1.6x faster.

Topic labels added: performance, docx.

CI safety: no concern found. A maintainer must still approve CI.

2 related pull request(s) in the same code
PR State Signal Assessment
#4862 open referenced as #4862; changes the same code related: PR #4862 memoizes get_style at the python-docx part level; this PR caches the resolved basedOn chain at the backend level. Both reduce style-lookup cost but target different layers, and the PR description confirms the gains stack.
#4587 open changes the same code related: PR #4587 touches _iter_paragraph_content and _get_paragraph_elements to split paragraphs at page/column breaks, which overlaps with this PR's caching of paragraph content, but the two changes serve different purposes.

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 424240460f. It read the code and did not run it.

Performance-only change to msword_backend.py. The two new caches are correctly scoped to the backend instance (per DOCX file) and use element identity checks. The paragraph-content cache is a size-1 memoizer that hits exactly when _get_paragraph_elements and _get_paragraph_text are called in sequence for the same paragraph inside _handle_text_elements. No public API or method signature changes. The one thing to verify is that the _get_style_chain cache key (a style XML element object) cannot be mutated or reused across different DOCX files, which the instance-level __init__ assignment guarantees.

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

❌ Patch coverage is 83.87097% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
docling/backend/msword_backend.py 83.87% 2 Missing and 3 partials ⚠️

📢 Thoughts on this report? Let us know!

@ExtReMLapin

Copy link
Copy Markdown
Contributor

Feel free to check if it can apply to other backends, like odt/odf

@deadstrobe5

Copy link
Copy Markdown
Contributor Author

I had a look at the OpenDocument backend. It already wraps get_style in functools.cache, which is the same lookup this PR caches for DOCX, and conversion scales linearly: text_document_01.odt repeated 5, 20 and 40 times converts in 23, 42 and 80 ms. About a third of that is reading text-style properties per run, so there is maybe 1.3x left on small absolute times, not the 1.6x seen on DOCX. I would rather not grow this PR for that, but I can open a separate one if you think it is worth it.

@ExtReMLapin

Copy link
Copy Markdown
Contributor

If it’s a win, probs worth opening a PR, that’s just my opinion, I agree with you, another PR is better

@deadstrobe5

Copy link
Copy Markdown
Contributor Author

@ExtReMLapin it was a win after all, just not where I expected: style lookups on Word-exported ODTs, 1.6x-3.2x on real files. Opened #4881 for it.

@dolfim-ibm dolfim-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 7bb935a 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:ci-safe AI triage found no CI-safety concern (advisory) 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.

3 participants