XSLT转换效率

9
我是一名支持工程师,我们公司的产品允许使用XSLT转换来自定义输出。
我为此制作了一个xsl转换。对于典型大小的源文件(几百KB),它能够很好地工作,但偶尔会出现真正巨大的源文件(10M)。在这种情况下,即使我让它运行数天,也无法生成输出。
软件工程团队进行了测试,并发现对于所涉及的转换和大型源文件而言,如果我们的产品编译为使用.Net 1.1中的转换引擎,则确实非常缓慢(>天),但如果他们将其编译为使用.Net 2.0,则速度相当快(约1-2分钟)。
显然,长期解决方案是等待下一次发布。
短期内,我想知道以下内容: 1)XSLT是否足够灵活,有更有效和不那么有效的方法来实现相同的结果?例如,可能是我构建xsl的方式导致转换引擎必须多次迭代源文件的开头,随着下一个结果片段离开开头越来越远,时间越来越长吗? (Schlemiel the Painter),或者 2)它更依赖于转换引擎如何解释xsl?
如果是第二种情况,我不想浪费太多时间来改进xsl(我不是一个大型xsl天才,已经很难实现我所做的微不足道的东西了...)。
谢谢!

我之前没听说过这个 :) http://en.wikipedia.org/wiki/Schlemiel_the_painter%27s_Algorithm - wprl
我最初是在http://www.joelonsoftware.com/articles/fog0000000319.html上听说的“奇怪的循环”,这也是我了解StackOverflow的方式。 - KnomDeGuerre
5个回答

5

我不熟悉.NET实现,但是一般情况下有几个方法可以加速处理大型文档:

  • 避免在Xpath表达式中使用"//",除非绝对必要。
  • 如果您只需要匹配Xpath表达式的第一个或唯一元素,请使用"[1]"限定符,例如"//iframe[1]"。许多处理器会对此进行优化。
  • 尽可能地,在处理大型XML输入时,看看是否可以设计一个基于流的解析器(如SAX)而不是基于DOM的解析器的解决方案。

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时,如果你添加了一个突然降低性能的模板匹配,请注释掉模板内的每个块。然后,逐个取消注释每个块,测试每次迭代的处理时间,直到找到导致问题的块。

3

To detect when to start a new section, I did this:

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

Could this be causing a lot or re-iteration?

当然。你选择的算法是O(N2),如果兄弟节点数量足够多,无论使用哪种编程语言实现,速度都会非常慢。

以下是一种使用键值的高效算法:

解决方案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+),这两个解决方案的转换时间如下:
Solution1:3.3秒。 Solution2:2.8秒。
使用XslTransform(.Net 1.1),Solution2运行了1622秒,即约27分钟。

1
我刚刚发现<xsl:stylesheet>和<xsl:key>指令没有显示 - 现在已经纠正了。 - Dimitre Novatchev

2
有一件值得检查的事情是,您的XSLT是否经常在XML文档的其他部分进行查找,即您在一个上下文节点中并查找另一个部分甚至是另一个文档中的值。如果您这样做,它可能会严重影响性能,您应该考虑使用xsl:key和key函数来解决。它告诉处理器在相关数据上实现快速查找索引。
我曾经构建过一个XSLT,它运行了8个小时(有很多交叉引用),但切换到使用键后,它的速度大大提高了。

1

在查找您的问题时,我在微软的知识库中找到了相关内容。您可以在这里查看。

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

如果您想尝试解决问题,可以在这里找到XSLT分析器。

否则,您可以查看微软网站上针对XSLT优化速度问题的链接(链接)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接