Freigeben über


Effective Xml Part 3: Didn’t you say XslCompiledTransform was fast?

“XslCompiledTransform is fast.”
“Really?”
“Yeah, XslCompiledTransform is fast… if used correctly.”

Indeed, depending on how you use XslCompiledTransform API your transformations can be really fast or not-that-fast (to say the least). The key to understanding where this difference comes from is to understan what XslCompiledTransform.Load() method actually does. The name of the method indicates that it is just used to load Xslt stylesheets. In reality the method does much more than this. It not only loads the Xslt stylesheet but it also compiles it to IL (Intermediate Language). Yes, after the stylesheet has been “loaded” somewhere there in memory is the real code built based on the provided stylesheet. Generating this code (i.e. “loading” the stylesheet) is costly but it allows for fast transformations – when you start a transformation in your app it no longer deals with the stylesheet, it just invokes the code the Xslt stylesheet was compiled to.

The transformation itself is a separate step (you invoke it by calling XslCompilledTransfom.Transform() method). It means that the code generated during the load phase is kept somewhere in the guts of the system waiting to be invoked. Unfortunately if the XslCompiledTransform instance used to load the Xslt stylesheet is gone the compiled code is gone as well and you need to load (so, compile) the stylesheet again to be able to perform a transformation. The conclusion is simple – to have fast transformations you need load the Xslt stylesheet once and reuse the object for further transformations. Otherwise (i.e. if you load the same stylesheet repeatedly) you will take the performance hit caused by loading (compilation) the stylesheet each time instead of only once.

But actually you can do even better... Since Xslt stylesheets are compiled to IL like for instance C# programs why not compile it beforehand and avoid the hit related to compilation at runtime at all? It is actually possible. There is this tool shipped with Visual Studio SDK called xsltc (xslt compiler) that does exactly this. It takes an Xslt stylesheet, compiles it and saves the result on a disk as an assembly. Now you can add a reference to the assembly in your project and invoke the transformation as you would invoke any other function from a referenced assembly. You should know however that xsltc has a few limitations. The biggest one is around paths – if you are using document() function the compiled stylesheets may not work correctly if you deploy your application to a different machine. You can find more information about xsltc in the MSDN: https://msdn.microsoft.com/en-us/library/bb399405.aspx

As far as the performance of transformations is concerned this one should be obvious… Use non-debug stylesheet versions in production environment. XslCompiledTransform constructor takes a flag called: enableDebug. If the flag is set to true you are able to debug your stylesheet in Visual Studio. It’s a great feature but as you can expect it does not come for free. When a stylesheet is compiled in the debug mode not only there is no optimization to the compiled code happening but also the compiler has to emit additional instructions to make the debugger happy. In the non-debug mode however the code being generated is heavily optimized – unused code is removed completely, expressions that can be calculated beforehand are converted to literals/constants at compile time, no unnecessary from execution point of view instructions are injected etc. Another important reason to not use debug mode in production is that when stylesheets are compiled debug mode XslCompiledStylesheet will create a dynamic assembly each time a stylesheet is loaded (this is necessary to be able to support debugging). Such assemblies cannot be unloaded and may result in OutOfMemory exceptions. So, if you want to make your transformations fast and reliable don’t load stylesheets in the debug mode when running in the production environment. The easiest way to not to have to remember about changing the enableDebug flag is to couple it with build version (you are not running Debug version of your assemblies in production environment, are you?). If you build a Debug version of your app the enableDebug flag is true, if you build a Release version enableDebug flag is false (use #if DEBUG compiler directive to achieve this).

Follow good coding practices. Might sound weird but really, not doing this can kill the performance of you transformations. The problem here is that under certain circumstances JITer (Just in Time compiler) stops optimizing the assembly code generated from the IL code the stylesheet was compiled to. This happens for instance when a method is too big or there is too many local variables. There is no need to be scared. It’s not easy to hit this issue. Stylesheets with templates (each template is compiled to a separate method) having a few hundred or even a thousand of lines should be fine (although a function consisting of 1000 lines feel too big from a “good coding practices” standpoint). As far as local variables are considered - it is not only about variables declared with xsl:variable instruction. Think about XPath – each step in the XPath expression is actually a loop and as such it needs a variable to iterate on. Again, even though the Xslt compiler (i.e. XslCompiledTransform) creates a lot of variables under the cover it is not easy to hit the problem. You would have to have thousands of them to see it. Still some people see it. And it is usually the combinations of both of the problems. A stylesheet contains a huge template (in the most common case the stylesheet is huge (e.g. 3 MB) and consists of just one template) – which results in a big method. Since the template is huge it contains a lot of XPath expressions – which results in a big number of local variables. As a result JITer gives up on any optimizations to the generated assembly code and transformations take ages. The solution is simple. Refactor the code. Why is the stylesheet so big? Probably because there is a lot of the same code copied all over the place (honestly, even though Xslt is verbose it’s not easy to come up with a well refactored stylesheet whose size is 3 MB – it’s 3145728 bytes which gives roughly 20000 lines (80 characters per line, 2 bytes per character), most likely 30000 lines – Copy/Paste must have been in use) – move the common code to a separate templates. Change this one huge xsl:choose statement to separate templates and invoke them using xsl:apply-templates instructions from appropriate places in your code. Do yourself and the person who will be maintaining the stylesheet when you are on vacation a favor. And make the transformation fast at the same time.

Pawel Kadluczka