fix(transpile): #6664 serialize XMIR into one string in classes.xsl - #6666
Conversation
The "serialize" template in classes.xsl wrote the XMIR of an object into "<xmir>" piece by piece - one instruction per indent, per angle bracket, per name and five per attribute. Since jcabi-xml runs every stylesheet DOM to DOM, each of those pieces became a separate DOM text node, so a single "<xmir>" of a large eo-runtime object ended up holding tens of thousands of them. Building those nodes, and then reading them back through a DOM that has to merge adjacent text to answer "text()", cost far more than the concatenation itself: the pass alone was two fifths of the whole transpile train, and xsline warned "XSL 'classes' took 4s". It is now an "xsl:function" returning one string, written with one "xsl:value-of", so "<xmir>" holds exactly one text node. The recursive call sits inside the enclosing "concat", which copies a subtree's string once per level of depth above it - measurably cheaper than returning a sequence of pieces and joining them at the end. Over all 170 sources of eo-runtime/src/main/eo, the pass drops from 21.5s to 1.0s and the whole nine-stylesheet train from 56.9s to 34.4s (the train gains more than the pass saves, because the downstream stylesheets no longer walk those adjacent text nodes). The output is byte-identical, both of this pass and of the train that ends in the generated Java. The class-matching pattern loses its redundant second "eo:atom" call at the same time: "not(A) or (A and B)" is "not(A) or B". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GevVXivd1KDNnCHqKW9rua
|
There was a problem hiding this comment.
Pull request overview
Improves transpilation performance by changing how classes.xsl serializes per-object XMIR into the generated <xmir> text used downstream for JavaDoc generation, ensuring the result is produced as a single DOM text node instead of tens of thousands of adjacent nodes.
Changes:
- Replaces the recursive
serializenamed template with aneo:serialize()xsl:functionthat returns a singlexs:string, emitted via onexsl:value-of. - Simplifies the class-selection match predicate by removing redundant
eo:atom(.)work and usingexists(...)instead ofcount(...) > 0.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| <!-- An object with neither data nor children is written as a single self-closing tag. --> | ||
| <xsl:variable name="void" as="xs:boolean" select="$content = '' and empty($kids)"/> |
There was a problem hiding this comment.
Keeping it, since the convention it appeals to isn't the one in this codebase: _funcs.xsl — the library this stylesheet imports — carries ten inline comments inside function bodies (<!-- 2-byte sequence: 110xxxxx 10xxxxxx -->, <!-- 55296 = 0xD800 --> and so on), all of them annotating a single line the way this one does.
The line above $void explains what the variable means — that an object with neither data nor children is written as a self-closing tag — while the doc comment above the function is about why the function has this shape at all (DOM text nodes, and the two slower shapes that were measured and rejected). Folding a one-line note about output format into that paragraph would bury it, and it belongs next to the variable it names.
Generated by Claude Code
🚀 Performance AnalysisAll benchmarks are within the acceptable range. No critical degradation detected (threshold is 100%). Please refer to the detailed report for more information. Click to see the detailed report
|
|
@yegor256 Thanks for the contribution! You've earned +8 points for this: +16 as a basis; -8 for the lack of code review. Please, keep them coming. Your running score is +2874; don't forget to check your Zerocracy account too). |



Closes #6664.
classes.xslwrites the XMIR of each top-level object into<xmir>, as the textto-java.xslturns into the Javadoc of the generated class. Theserializetemplate spelled that text out piece by piece — one instruction per indent, per angle bracket, per name and five per attribute.jcabi-xmlruns every stylesheet DOM to DOM (DOMSource→DOMResult), so each piece became a separate DOM text node: a single<xmir>forstring/printf.eo(~1.6k elements, ~8.3k attributes) held roughly 50k of them. Building them, and then reading them back through a DOM that has to merge adjacent text nodes to answertext(), cost far more than the concatenation itself — henceXSL 'classes' took 4s (over 500ms)from xsline'sStFaston a single XMIR.It is now an
xsl:functionreturning onexs:string, written with onexsl:value-of, so<xmir>holds exactly one text node.Numbers
Saxon-HE 13.0, all 170 sources of
eo-runtime/src/main/eoparsed withEoSyntaxand put throughset-locators.xsl+set-original-names.xslfirst, best of two warm runs, single-threaded:classes.xslalonestring/printf.eo)21x for the pass, 1.7x for the train. The train gains more than the pass saves, because the eight downstream stylesheets no longer walk those adjacent text nodes either. For reference, replacing the serializer with an empty
<xmir/>altogether takes the pass to 0.14s, so what is left is close to the floor.Why nested
concatand not a joined sequenceTwo other shapes were measured on the 21 largest sources (baseline 15.2s there):
xs:string*, onestring-joinat the endxs:string, recursive call insideconcat(this PR)Nesting the recursive call copies a subtree's string once per level of depth above it, which at the depth of a real XMIR is far cheaper than Saxon's per-item bookkeeping. The rejected shapes are noted in the comment on the function so the next reader does not re-try them.
Correctness
eo-runtimesources — both of this pass alone, and of the full nine-stylesheet train, i.e. of the generated Java.MjTranspileTest(52 transpile packs) andFingerprintTest: 57 tests, all green.xcopclean on the changed file.Transpilation.XSLS, so existing caches invalidate on their own.Also in this diff
The class-matching pattern loses a redundant
eo:atomcall and acount(...) > 0:not(A) or (A and B)isnot(A) or B. Not measurable next to the serializer, but it is the same question asked once instead of twice.Generated by Claude Code