XSLT转换效率

时间:2008-10-22 19:10:04

标签: .net xslt

我是一名支持工程师,我们公司的产品允许XSLT转换定制输出。

我为此目的进行了xsl转换。它适用于典型大小的源文件(几个100k),但偶尔会有一个非常大的(10M)源文件。在这种情况下,即使我让它磨了几天也不会产生输出。

SW工程团队测试了它并发现对于转换和大型源文件确实非常慢(>天),如果我们的产品被编译为使用.Net 1.1中的转换引擎,但是如果它们编译它与.Net 2.0一样,速度很快(约1-2分钟)。

长期解决方案显然是等待下一个版本。

从短期来看,我想知道以下几点: 1)XSLT是否足够灵活,是否有更高效,更低效的方法来实现相同的结果?例如,有可能我构建xsl的方式,转换引擎必须多次从源文件的开头迭代,越长越长,因为下一个结果片从一开始就越走越远? (Schlemiel the Painter),或 2)它是否更依赖于变换引擎如何解释xsl?

如果是2,我不想浪费大量时间来改进xsl(我不是一个很大的xsl天才,我很难实现我做的很少......)

谢谢!

5 个答案:

答案 0 :(得分:5)

我不熟悉.NET实现,但是通常可以做一些事情来加速大型文档的处理:

  • 除非绝对必要,否则请避免在Xpath表达式中使用“//”。
  • 如果您只需要与Xpath表达式匹配的第一个或唯一元素,请使用“[1]”限定符,例如“// iframe中[1]”。许多处理器都为此实现了优化。
  • 只要有可能,在处理大量XML输入时,看看您是否可以围绕基于流的解析器(如SAX)而不是基于DOM的解析器设计解决方案。

答案 1 :(得分:4)

通常,如果您发现处理时间与输入大小的非线性增加,您应该怀疑代码多于框架。但是,当使用.NET 2.0编译该工具时问题就消失了,所有的赌注都没有了。

使用XSLT,如果您使用直接模板匹配进行所有解析,则很难创建非线性性能曲线:

<xsl:template match="foo">
  <!--OUTPUT-->
  <xsl:apply-templates / >
  <!--OUTPUT-->
</xsl:template>

 <xsl:template match="bar">
  <!--OUTPUT-->
  <xsl:apply-templates / >
  <!--OUTPUT-->
</xsl:template>

请注意您可能使用<xsl:for-each> 进行解析的任何地方;模板匹配几乎总是获得相同结果的更好方法。

解决此性能问题的一种方法是一次重新创建一个模板匹配的XSLT,在添加每个匹配项后测试处理时间。你可以从这场比赛开始:

<xsl:template match="*">
  <xsl:copy>                   <!--Copy node                   -->
    <xsl:copy-of select="@*"/> <!--Copy node attributes         -->
    <xsl:apply-templates />    <!--Process children             -->
  </xsl:copy>
</xsl:template>

这将匹配并将每个节点(一次一个)复制到新文档。这不应该表现出处理时间与输入大小的非线性增加(如果确实如此,则问题不在于您的XSLT代码)。

在重新创建XSLT时,如果添加突然杀死性能的模板匹配,请注释掉模板中的每个块。然后,一次取消注释一个块,测试每次迭代的处理时间,直到找到导致问题的块为止。

答案 2 :(得分:3)

  

要检测何时开始新的部分,我这样做了:

<xsl:if test="@TheFirstCol>preceding-sibling::*[1]/@TheFirstCol"
     

这会导致很多或重复吗?

当然。您选择的算法是O(N 2 ),并且在有足够数量的兄弟姐妹的情况下会非常慢,无论实现语言如何。

这是一个使用密钥的有效算法:

解决方法1:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:output method="text"/>

 <xsl:key name="kC1Value" match="@c1" use="."/>

    <xsl:template match="/">
      <xsl:for-each select="*/x[generate-id(@c1) = generate-id(key('kC1Value',@c1)[1])]">

       <xsl:value-of select="concat('&#xA;',@c1)"/>

       <xsl:for-each select="key('kC1Value',@c1)">
         <xsl:value-of select="'&#xA;'"/>
         <xsl:for-each select="../@*[not(name()='c1')]">
           <xsl:value-of select="concat('   ', .)"/>
         </xsl:for-each>
       </xsl:for-each>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

不幸的是,XslTransform(.Net 1.1)的generate-id()函数的执行效率非常低。

使用XslTransform可能会更快:

溶液2:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

 <xsl:output method="text"/>

 <xsl:key name="kC1Value" match="@c1" use="."/>

    <xsl:template match="/">
      <xsl:for-each select="*/x[count(@c1 | key('kC1Value',@c1)[1]) = 1]">

       <xsl:value-of select="concat('&#xA;',@c1)"/>

       <xsl:for-each select="key('kC1Value',@c1)">
         <xsl:value-of select="'&#xA;'"/>
         <xsl:for-each select="../@*[not(name()='c1')]">
           <xsl:value-of select="concat('   ', .)"/>
         </xsl:for-each>
       </xsl:for-each>
      </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

应用于以下小型XML文档时:

<t>
 <x c1="1" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="1" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="1" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="1" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="2" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="2" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="2" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="2" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="3" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="3" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="4" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="4" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="4" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="4" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="5" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="5" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="5" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="6" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="6" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="6" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="6" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="6" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="6" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="7" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="7" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="7" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="7" c2="1" c3="1" c4="0" c5="0"/>
 <x c1="8" c2="0" c3="0" c4="0" c5="0"/>
 <x c1="8" c2="0" c3="1" c4="0" c5="0"/>
 <x c1="8" c2="2" c3="0" c4="0" c5="0"/>
 <x c1="8" c2="1" c3="1" c4="0" c5="0"/>
</t>

这两种解决方案产生了想要的结果:

1
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
2
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
3
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
4
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
5
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
   0   0   0   0
   0   1   0   0
6
   2   0   0   0
   1   1   0   0
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
7
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0
8
   0   0   0   0
   0   1   0   0
   2   0   0   0
   1   1   0   0

从上面的小XML文件中,我通过复制每个元素6250次(使用另一个XSLT转换:)生成了一个10MB的XML文件。)。

使用10MB xml文件和XslCompiledTransform(.Net 2.0 +),这两种解决方案具有以下转换时间:

  

解决方案1:3.3秒   解决方案2:2.8秒。

使用XslTransform(.Net 1.1)解决方案2运行1622秒。那是大约27分钟。

答案 3 :(得分:2)

世界检查的一件事是你的XSLT是否经常查找XML文档的其他部分,即你在一个上下文节点中并在文档的另一部分甚至另一个文档中查找值。如果你这样做可能会很难达到性能,你应该考虑使用xsl:key和关键功能。它告诉处理器对相关数据实现快速查找索引。

我曾经构建过一个需要8个小时才能运行的XSLT(有很多交叉引用)并且切换到使用密钥会给它带来巨大的速度提升。

答案 4 :(得分:1)

在查找问题后,我在微软发现了KB。你可以看到它here

他们说.NET 1中的XSLT转换在性能方面存在一些问题,并且可以提供快速修复。

如果您想尝试解决问题,可以使用XSLT Profiler here

否则,您可以看到Microsoft网站上提供了哪些链接,以优化XSLT(link)的速度问题。