Poor optimization of `$doc1/descendant-or-self::node()/child::node()`
$doc1/descendant-or-self::node()/child::node() arises in the generated XSLT code produced by conversion of the QT3 test fn-innermost-053.
Here $doc1 has unknown type.
In Saxon 9.8 the composite path expression is translated to
conditionalSort(xxx, docOrder(($doc1 treat as node())/descendant::node())
Saxon 9.9 does not succeed in reducing the path expression to a single axis step in this way.
The advantage of rewriting
descendant::node() is that the latter naturally delivers nodes in document order; it is also a faster traversal of the TinyTree.
#2 Updated by Michael Kay about 3 years ago
Looking at how the original stylesheet is optimized in 9.8.
docOrder(($doc1) treat as node()/descendant-or-self::node())/child::node()
The LHS turns into
ConditionalSorter(exists(tail(($doc1) treat as node())), docOrder(($doc1) treat as node()/descendant-or-self::node()))
DocumentSorter line 108 turns the SlashExpression (of which the above is the LHS) into
ConditionalSorter(exists(tail(($doc1) treat as node())), docOrder((($doc1) treat as node()/descendant-or-self::node())/child::node()))
that is, it has moved the final /child::node step inside the ConditionalSorter, which paves the way for the descendant-or-self and child steps to be combined (which happens in SlashExpression.simplifyDescendantPath(), line 287)
The 9.9 optimization path also succeeds in folding the final child::node() step inside the ConditionalSorter, but it seems that the final simplifyDescendantPath() never happens.
Comparing the 9.8 and 9.9 optimization paths step-by-step, 9.8 having optimized the whole template body, goes into global variable extraction (which succeeds) and then does another optimization pass on the result. 9.9 has optimizer options set which inhibit global variable extraction, and the second optimization pass therefore doesn't happen. It's the second pass that recognizes the opportunity to simplify the descendant axis.
#3 Updated by Michael Kay about 3 years ago
The Configuration is initializing a defaultXsltCompilerInfo during its init sequence, at a point where the variable optimizerOptions holds the optimizer options for Saxon-HE; the value is subsequently changed to the set of options appropriate to Saxon-EE, but this is too late to affect the default XSLT compiler info object.
This causes 9.9 to generate essentially the same code as 9.8 for this example stylesheet.
However, this leaves open the question why two optimization passes should be needed to optimize this particular construct, and why it doesn't get optimized in the simple case of the direct query given in comment #1.
#4 Updated by Michael Kay about 3 years ago
The issue here seems to be that SlashExpression.simplifyDescendantPath is called during the type-checking phase, and is not called again during the optimization phase. So if optimization generates an expression that is amenable to this rewrite, it won't normally happen, unless we go back to repeat the type-checking, which is normally not encouraged.
Putting another call on simplifyDescendantPath() into the optimize phase solves the problem.
I won't do this for 9.8, however, in the interests of stability.
#5 Updated by Michael Kay about 3 years ago
- Status changed from New to Resolved
- Fix Committed on Branch 9.9 added
Resolved with three patches to 9.9:
(a) ensure that the defaultXsltCompilerInfo in an EnterpriseConfiguration uses all Saxon-EE optimization options
(b) put a call on simplifyDescendantPath() into SlashExpression.optimize()
(c) after rewriting sort(X)/Y as sort(X/Y) in DocumentSorter.optimize(), run an optimization pass over the new slash expression X/Y.
Please register to edit this issue