Skip to content

Add LIBXML_NOBLANKS to dom_load() XML parse options - #304

Closed
jordikroon wants to merge 1 commit into
php:masterfrom
jordikroon:performance-improvements
Closed

Add LIBXML_NOBLANKS to dom_load() XML parse options#304
jordikroon wants to merge 1 commit into
php:masterfrom
jordikroon:performance-improvements

Conversation

@jordikroon

@jordikroonjordikroon commented Jun 17, 2026

Copy link
Copy Markdown
Member

Drops whitespace-only text nodes. Which shouldn't be harmful. It results in a smaller DOM, thus faster to traverse and requires less memory.

Benchmarked configure.php --with-lang=en, 5 runs, PHP 8.5:

foriin 1 2 3 4 5;do
/usr/bin/time -l php configure.php --with-lang=en >/dev/null
done2>benchmark.txt

Benchmark:

BeforeAfter
Time8.94s6.28s
Peak memory1687 MB1263 MB

Raw files:

Before patch (baseline)
=== baseline run 1 ===
8.94 real 5.79 user 2.19 sys
1703395328 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
178083 page reclaims
2166 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
23136 voluntary context switches
10176 involuntary context switches
49464100831 instructions retired
15879556244 cycles elapsed
1687130560 peak memory footprint
=== baseline run 2 ===
8.36 real 5.91 user 3.10 sys
1745633280 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
137481 page reclaims
410 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
2017 voluntary context switches
11477 involuntary context switches
50933601605 instructions retired
18544221387 cycles elapsed
1687310784 peak memory footprint
=== baseline run 3 ===
9.61 real 6.00 user 3.05 sys
1650540544 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
226507 page reclaims
2176 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
13407 voluntary context switches
17884 involuntary context switches
50502793793 instructions retired
16581835963 cycles elapsed
1687376320 peak memory footprint
=== baseline run 4 ===
6.83 real 5.81 user 1.75 sys
1761476608 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
137900 page reclaims
391 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
271 voluntary context switches
6098 involuntary context switches
48201588467 instructions retired
14541027505 cycles elapsed
1687343552 peak memory footprint
=== baseline run 5 ===
10.23 real 6.30 user 3.08 sys
1748795392 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
138801 page reclaims
2065 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
15892 voluntary context switches
25486 involuntary context switches
51353051988 instructions retired
18729227016 cycles elapsed
1687310784 peak memory footprint
After patch
=== patched run 1 ===
6.40 real 5.35 user 1.83 sys
1306656768 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
111176 page reclaims
486 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
967 voluntary context switches
7422 involuntary context switches
42787245602 instructions retired
13223610026 cycles elapsed
1263193728 peak memory footprint
=== patched run 2 ===
5.64 real 5.08 user 1.57 sys
1313062912 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
111184 page reclaims
371 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
241 voluntary context switches
3070 involuntary context switches
42706120047 instructions retired
12344963394 cycles elapsed
1263554176 peak memory footprint
=== patched run 3 ===
9.44 real 5.80 user 3.54 sys
1300905984 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
111008 page reclaims
1975 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
8799 voluntary context switches
16634 involuntary context switches
45579012902 instructions retired
16727081777 cycles elapsed
1263423104 peak memory footprint
=== patched run 4 ===
6.28 real 5.24 user 1.75 sys
1243627520 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
131634 page reclaims
477 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
1024 voluntary context switches
8285 involuntary context switches
42888761617 instructions retired
13168907040 cycles elapsed
1263324800 peak memory footprint
=== patched run 5 ===
5.81 real 5.22 user 1.61 sys
1309032448 maximum resident set size
0 average shared memory size
0 average unshared data size
0 average unshared stack size
111131 page reclaims
396 page faults
0 swaps
0 block input operations
0 block output operations
0 messages sent
0 messages received
0 signals received
168 voluntary context switches
3945 involuntary context switches
42648784251 instructions retired
12410583670 cycles elapsed
1263062656 peak memory footprint

@jordikroon
jordikroon requested review from Girgias and alfsbJune 17, 2026 20:04
@alfsb

Copy link
Copy Markdown
Member

As commented on discord, I know little to nothing about PhD in this respect.

Why PhD? Because some Docbook parts specify that whitespace should be preserved in the rendering process, so some whitespace may end up being relevant in PhD, or in final output.

To get better performance and reduce memory, removing XML comments may give a similar impact, and these are unspecified in Docbook.

@alfsb

Copy link
Copy Markdown
Member

Philip O gives an example where this causes a problem. The rendering of ltrim() will be broken, because two consecutive <acronym> separated by space will be source transformed, and then rendered, as two consecutive <acronym> glued together.

The problem is more general. Whitespace in Docbook is complicated. In some elements it is completely irrelevant, and could be trimmed, but there are some other contexts, where whitespace should be coalesced (like HTML), and other contexts where it should be fully preserved.

There is a hint in old libxml docs:

The 2.x and later version will switch to the XML standard way and ignorableWhitespace() are only generated when running the parser in validating mode and when the current element doesn't allow CDATA or mixed content.

So... it may be possible to change libxml to the "correct" Docbook behaviour if it is called in validating mode.

@alfsb

Copy link
Copy Markdown
Member

After some reading, I think that LIBXML_NOBLANKS may only work with DTD schemas. That is because, Docbook is officially defined as RelaxNG, and

RELAX NG does not define a way for an XML document to associate itself with a RELAX NG pattern.

so DOMDocument->validateOnParse will only work with schemas that do that, and libxml2 has validateOnParse support. Namely, DTD schemas. @ndossche may correct me here.

The last time Docbook published a DTD schema was on version 5.0.1 AFAIK. And I do not know a way to convert a RNG into a DTD, even imperfectly. And obviously, enabling validateOnParse against a full Docbook specification may cause performance regression.

Maybe it is possible to just fake it. To bruteforce a "one rule per element" DTD schema that contains all pairwise parent vs child-element-or-text pairs. The theory is that libxml only needs to know if a particular element may contain #PCDATA, so LIBXML_NOBLANKS will not be active in these elements.

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

This cannot be merged as is. As commented above, this changes the rendering of the manual in broken ways.

It may be possible, though. It is only a conjecture, but by creating a simplified DTD from RNGs (preferably) or from a validated manual (for experimental use only), with the format:

<!ELEMENT parent-element (child1|child2|child3)*>
<!ELEMENT parent-element (#PCDATA|child1|child2)*>

and enabling it on doc-base/manual.xml, may cause libxml2 to trim spaces only they not cause rendering changes.

The presence or absence of #PCDATA should be calculated by detecting if any of their possible direct child nodes is expected text nodes (from RNG) or is a text node not composed entirely of whitespace (from build XML).

@ndossche

Copy link
Copy Markdown
Member

so DOMDocument->validateOnParse will only work with schemas that do that, and libxml2 has validateOnParse support. Namely, DTD schemas. @ndossche may correct me here.

This is right

@alfsb

alfsb commented Jul 3, 2026

Copy link
Copy Markdown
Member

so DOMDocument->validateOnParse will only work with schemas that do that, and libxml2 has validateOnParse support. Namely, DTD schemas. @ndossche may correct me here.

This is right

Thanks for the confirmation! I will try to do some experiments in the following days.

@alfsb

alfsb commented Jul 8, 2026

Copy link
Copy Markdown
Member

Tried everything I could. LIBXML_NOBLANKS use pure XML conventions for what is considered insignificant whitespace (wsi). The libxml code paths for wsi do not consider any DTD configurations that I'm aware of. And these XML wsi conventions simply diverge from that on what Docbook and/or RelaxNG considers wsi, so this option cannot be used.

I think there are other ways to trim XML and accelerate building and validating, but it will take time to develop any of this, in another PR. For now, I'm focusing on completing do-not-translate and DTD to XML entity convention.

@alfsbalfsb mentioned this pull request Jul 8, 2026
47 tasks
@alfsb

Copy link
Copy Markdown
Member

See PR 321 for my take on this. Got no time reduction, but some good reductions in peak memory and file size.

@jordikroon

Copy link
Copy Markdown
MemberAuthor

Closing this since as this is not working the way it is meant to be.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@jordikroon@alfsb@ndossche