I know that in most languages there are gotchas that can really drain your code’s performance, such as referencing Classes in JavaScript or using Microsoft expressions in your CSS.

Are there any real gotchas with XSLT that can have an impact over performance? Such as using Param instead of Variable, templates instead of copy-of

A few that I can think of:

  • I believe most of the time for-each is more costly than apply-templates
  • Avoid using wildcard selectors like * or // when possible
  • Avoid using count() when possible
  • I remember reading somewhere Michael Kay talked about not processing the same nodeset more than once, but rather saving it to a variable. He also said it’s preferable to using conditionals within a template than to have really complex matching statements.

There is a cool article on Saxon Diaries about the 10 most common mistakes to make — not everything is performance-oriented, but nevertheless it is interesting to read.

I’ll dredge through and see if I can find the list I did up a couple of years ago - a quick correction though: Craig, I’ve always found for-each to be significantly faster than apply-templates when iterating (rather than selecting).

Maybe this is something I’ve incorrectly carried with me from the Java-based transformers?

Ah, maybe I got them reversed. I’m battling a cold or something at the moment and haven’t been able to think very clearly…

Oh, suuuure. That’s your excuse, is it? ;)

for-each is indeed faster computationally but the performance gain is usually negligible when it comes to the practical applications through Symphony. i.e. to see a significant gain in using for-each over apply-templates, the processing power and memory required for your DSs to generate a large enough dataset would be a much bigger cause for concern.

However, for-each is still great if the XML is coming from outside of PHP processed sources. In some ways, a static XML DS would mitigate the processing issue but memory requirement isn’t mitigated (although memory is usually less of a concern in modern environments).

I would say that the main benefit of using for-each over apply-templates would be in the form of code readability. In some instances, overly abstracting one’s XSLT document sometimes makes it harder to follow. It’s better to use for-each for simple logic loops and in-context node-set looping.

I’ll add two more performance tips:

  • use xsl:key when you can. This is akin to generating a hash or index table in programming and data structure speak.
  • always use tail-end recursion when you can.

Tail-end recursion?

Allen, do you know a good introduction to xsl:key?

Tail-end recursion is a type of recursion that eliminates the need for processors to store a cumulative amount of information for every recursive depth. For example, if a recursive template sums the total from 1 to 3, you could recursively calculate the value in two ways:

  • 1 + (1 + 1 + (1 + 1 + 1))
  • (1 + 0) -> (2 + 1) -> (3 + 3)

In the former, the recursion progressively add values based on each recursive depth. The processor has to hold on to memory of all the values until the end of the recursion. Every depth needs to return to the level above its value for calculation.

In the latter, you pass the sum of the previous values as a variable to the next level of recursion. So on first recursion, pass the value of 0 as the new variable down. On second recursion, pass the value of 1 as the new variable down. On third recursion, pass the value of 3 as the new variable down. In every level, the templates gets a summation of the previous calculations, so there is no need to hold individual values all the way to the end of the recursion then back up the tree to calculate.

Essentially this way, at any given step of the recursion tree, the processor is not required to hold the values in all previous recursion levels.

This all boils down to two main points when making a recursive template:

  1. Pass the result value into the next template call.
  2. Place the self-calling template as the last instruction of the template.
  3. Make sure the template only calls itself once at each given level.

I’ve not really explained the concept very well, so I’d encourage anyone who’s interested to look up tail recursion. Jeni Tennison’s “Beginning XSLT” book has a good explanation on tail recursion.

Allen, do you know a good introduction to xsl:key?

Not really unfortunately. I think it’s best to simply toy with it and see how it works. Once you’ve grasp the concept, it’s not too bad.

Essentially, you use two things to establish your “lookup table”: xsl:key and key().

xsl:key is an XSLT instruction that creates the lookup table. This instruction needs to be a top level element. This instruction needs to know which nodes to use to generate the table, and what the ‘identifying key value’ should be. i.e. a product number or an entry ID.

key() is the function that does the lookup. You give the function the ‘identifying key value’, i.e. Product ID of “Q1231” and it will return you a node-set of all matching elements.

Tail recursion explanation on a great article on IBM’s Developer works site…

Recursion Optimization 4: Tail Recursion

If you already have some experience with recursion, you are probably wondering why I didn’t mention Tail Recursion as the first optimization technique, since this technique can eliminate recursion depth completely without introducing any substantial overhead. Despite the advantages this technique offers, it requires that the XSL engine doing the transformation recognize the presence of the technique within the XSL code and then change its behavior to accommodate the technique. Unfortunately, most XSL engines do not yet offer such a feature. The good news is that the overwhelming acceptance of XSL in the business and academic worlds means that this limitation will not be with us too much longer. It is therefore important for developers to understand how Tail Recursion works, as it may eliminate the memory problems many now face without hindering performance in any substantial way. The basic idea behind Tail Recursion is to eliminate the storage of any state information across the recursive steps. All information that would ever be needed at any single step is passed as a parameter to the function instead of being stored at some higher level in the recursion stack. This allows the XSL engine to treat the recursive function as though it were an iterative loop in a procedural language. Take a look at Listing 10 for the price sum example in Tail Recursive form:

Listing 10. Node traversal using Tail Recursion

<xsl:template name="priceSumTail">
  <xsl:param name="productList"/>
  <xsl:param name="result" select="0"/>
    <xsl:when test="$productList">
      <xsl:call-template name="priceSumTail">
        <xsl:with-param name="productList"
          select="$productList[position() &gt; 1]"/>
        <xsl:with-param name="result"
          select="$result + 
    <xsl:otherwise><xsl:value-of select="$result"/></xsl:otherwise>

The addition of the extra result variable makes all of the difference. Instead of stacking numbers to add at the various recursive steps, the addition is done at each stage and the result passed along as a parameter to the next recursion step. A smart XSL engine would simply overwrite the memory space holding the result value on each call in the same manner that a variable value is overwritten in Java code or C. This technique allows the language to take advantage of the features of both declarative and imperative language processing without changing the nature of XSLT as a programming language.

@Allen - is your recursion screencast a tail recursion? Also, is that posted anywhere else online (since it looks like the old site is now gone or at least down)?

Actually, if those little xslt tutorial videos could be posted on the “Learn” section of the Symphony site that would be awesome.

Allen, do you know a good introduction to xsl:key?

It’s dated but I use a key in this image tutorial.

I like naming my keys x-by-y (i.e. images-by-entry) because it reminds me very quickly of its purpose. Great for code readability.

Does anyone have any metrics about the savings that using keys provides? For the size of XML that Symphony deals with (an average page) are we talking mere fractions of milliseconds, or as much as tenths of a second? I never really look at the creation time of the XSLT in the ?profile view. Maybe I should!

I’d be interested on this as well.

Me too. All I know at the moment is that it’s faster in theory:

This produces the desired output, but the complexity of the XPath expression means that if you have a lot of shirt elements whose colors need to be looked up, creating the result tree could go slowly. Declaring and using keys can make it go much faster, because an XSLT processor that sees that you’ve declared a key usually sets up an index in memory to speed these lookups. Doing it this way can produce the same result as the previous stylesheet much more efficiently.


I’d love to see some numbers!

From my humble experience I can give one example where using keys made a major difference in speed. I could reduce the time to convert a set of around 500 differently nested elements to JSON from around 5 seconds to 200 milliseconds. But it greatly depends on the structure of the template. When you know exactly where to go in your XPath you probably don’t need keys. But when you do some kind of search (as Michael said) or when you need to access different subsets of a set of elements over and over, then using keys is the way to go.

This thread is old, but I came across something really interesting: XSL Cache. If it works as advertised, then perhaps the Symphony developers can make it an option to take advantage of it? Just a thought.

I remember finding that ages ago. Curious if anyone has managed to try it out. From the website it seems like a relatively drop in solution that requires a minor change to Symphony's XsltProcess class.

Create an account or sign in to comment.

Symphony • Open Source XSLT CMS

Server Requirements

  • PHP 5.3-5.6 or 7.0-7.3
  • PHP's LibXML module, with the XSLT extension enabled (--with-xsl)
  • MySQL 5.5 or above
  • An Apache or Litespeed webserver
  • Apache's mod_rewrite module or equivalent

Compatible Hosts

Sign in

Login details