Skip to content

Improve loading performance. - #2094

Open
jscarle wants to merge 4 commits into
dotnet:mainfrom
jscarle:main
Open

jscarle wants to merge 4 commits into
dotnet:mainfrom
jscarle:main

Conversation

@jscarle

@jscarle jscarle commented Jun 10, 2026

Copy link
Copy Markdown

Improves DOM population performance by reducing per-element overhead in the OpenXML SDK hot path: child element factory lookup no longer allocates a temporary lookup object for every XML node, tiny child factory lists use a cheaper linear scan while larger lists keep binary search, Populate reuses cached namespace resolver and markup-compatibility state, skips no-op MC processing work in the default NoProcess mode, and attribute loading avoids repeated/cold lookups by caching stable values. These changes are internal-only and preserve existing load semantics while making large worksheet/document materialization cheaper.

@twsouthwick

Copy link
Copy Markdown
Member

Do you have evidence this is faster? there are some benchmarks (that unfortunately we keep forgetting about) that may be useful as a starting point

@jscarle

jscarle commented Jun 18, 2026

Copy link
Copy Markdown
Author

Sure, this is what I was able to measure:

  Environment: BenchmarkDotNet 0.15.8, .NET SDK 10.0.204, .NET runtime 10.0.8, Windows 11, Intel Core Ultra 7 155H.

   Benchmark                                           Base          PR     Time change    Base alloc    PR alloc    Alloc change
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━  ━━━━━━━━━━  ━━━━━━━━━━━━━━  ━━━━━━━━━━━━  ━━━━━━━━━━  ━━━━━━━━━━━━━━
   Worksheet DOM from outer XML                    39.46 ms    33.83 ms    14.3% faster      15.14 MB    12.47 MB      17.6% less
  ──────────────────────────────────────────────  ──────────  ──────────  ──────────────  ────────────  ──────────  ──────────────
   Wordprocessing DOM from outer XML               37.68 ms    27.41 ms    27.3% faster      12.54 MB     9.12 MB      27.3% less
  ──────────────────────────────────────────────  ──────────  ──────────  ──────────────  ────────────  ──────────  ──────────────
   .xlsx package open + worksheet DOM traversal    53.06 ms    43.90 ms    17.3% faster      15.30 MB    12.63 MB      17.5% less

The strongest evidence is the allocation reduction: all three scenarios allocate materially less memory per operation, and the wall-clock timings move in the same direction.

@jscarle

jscarle commented Jun 19, 2026

Copy link
Copy Markdown
Author

@twsouthwick Would you like additional performance testing?

@mikeebowen
mikeebowen requested a review from twsouthwick July 21, 2026 16:09
@mikeebowen

Copy link
Copy Markdown
Collaborator

@github-actions

github-actions Bot commented Aug 18, 2026 •

Copy link
Copy Markdown

Test Results

    80 files  ±  0      80 suites  ±0   1h 26m 16s ⏱️ - 6m 24s
 2 078 tests ±  0   2 075 ✅ ±  0   3 💤 ±0  0 ❌ ±0 
38 003 runs   - 172  37 961 ✅  - 172  42 💤 ±0  0 ❌ ±0 

Results for commit bb60385. ± Comparison against base commit 431ab05.

♻️ This comment has been updated with latest results.

@jscarle

jscarle commented Aug 19, 2026

Copy link
Copy Markdown
Author

@twsouthwick Any chance we can review this?

@twsouthwick twsouthwick 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.

there are two changes going on here - can you explain why each are needed? I'd like to see each as their own change to understand if they are needed.

Comment thread src/DocumentFormat.OpenXml.Framework/OpenXmlCompositeElement.cs
@jscarle

jscarle commented Aug 25, 2026

Copy link
Copy Markdown
Author

There are two separate optimizations that work together to improve the combined benchmark performance during loading.

The first changes how child elements are found. Every lookup used to create a temporary ElementFactory object just so Array.BinarySearch could search for it. The new implementation compares the element name directly, avoiding that allocation. It uses a simple scan when there are four or fewer possible children because checking a few entries directly is cheaper. Larger collections still use binary search so they remain efficient.

The second change reduces repeated work while loading children and attributes. Values that remain the same during loading, such as:

  • the element context
  • markup-compatibility settings
  • target version
  • the namespace resolver
  • strict-relationship flag
    are calculated once and reused.

It also skips markup-compatibility tracking entirely when processing is disabled.

@jscarle

jscarle commented Sep 8, 2026 •

Copy link
Copy Markdown
Author

@twsouthwick Still looking to merge this if possible as it improves loading performance in very large spreadsheets.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants