【问题标题】:XSLT: poor performance due to complex XPath expression?XSLT:由于复杂的 XPath 表达式导致性能不佳?
【发布时间】:2011-12-11 15:08:55
【问题描述】:

在 XSLT 程序的某个时刻,我有以下内容:

<xsl:for-each select="tags/tag">
    <xsl:apply-templates select="//shows/show[film=//films/film[tag=current()/@id]/@id]|//shows/show[group=//groups/group[film=//films/film[tag=current()/@id]/@id]/@id]">
        <xsl:sort select="date" data-type="text" order="ascending"/>
        <xsl:sort select="time" data-type="text" order="ascending"/>
        </xsl:apply-templates>
</xsl:for-each>

看起来相当复杂的 XPath 表达式//shows/show[film=//films/film[tag=current()/@id]/@id]|//shows/show[group=//groups/group[film=//films/film[tag=current()/@id]/@id]/@id] 大大减慢了程序的执行速度(与添加引用的代码之前的执行时间相比——当然,处理相同的数据)。

由于表达式的相对复杂性,您认为这是否正常,您是否看到我可以如何改进它以使其表现更好?

注意:在 XPath 表达式中,film//films/filmgroup//groups/group 指的是不同的元素。

请参阅下面的 XML 输入的精简示例。

<program>
  <tags>
    <tag id="1">Tag1</tag>
    <tag id="2">Tag2</tag>
    <tag id="3">Tag3</tag>
  </tags>
  <films>
    <film id="1">
        Film1
      <tag>2</tag><!-- References: /program/tags/tag/@id=2 -->
    </film>
    <film id="2">
        Film2
      <tag>1</tag><!-- References: /program/tags/tag/@id=1 -->
    </film>
    <film id="3">
        Film3
      <tag>3</tag><!-- References: /program/tags/tag/@id=3 -->
    </film>
    <film id="4">
        Film4
      <tag>3</tag><!-- References: /program/tags/tag/@id=3 -->
    </film>
  </film>
  <groups>
    <group id="1">
      <film>3</film><!-- References: /program/films/film/@id=3 -->
      <film>4</film><!-- References: /program/films/film/@id=4 -->
    </group>
  </groups>
  <shows>
    <show id="1"><!-- Show with film (=simple) -->
      <film>1</film><!-- References: /program/films/film/@id=1 -->
      <date>2011-12-12</date>
      <time>12:00</time>
    </show>
    <show id="2"><!-- Show with group (=combined) -->
      <group>1</group><!-- References: /program/groups/group/@id=1 -->
      <date>2011-12-12</date>
      <time>14:00</time>
    </show>
  </shows>
</program>

解释:

  • 标签是附加到电影的属性(实际上,它是一个类别)。
  • 组是电影的枚举。
  • 节目参考电影或团体。
  • 我想要什么:对于每个标签,我都在寻找引用具有当前标签的电影的节目和引用至少其中一部电影具有当前标签的组的节目。

【问题讨论】:

    标签: performance xslt xpath


    【解决方案1】:

    Double slashes in XPath 在处理大型文档时会占用性能和 CPU(因为必须评估文档中的每个节点)。如果你可以用绝对或相对路径替换它,你应该会有明显的改进。如果您可以发布输入架构和所需的输出,我们可以更具体吗?

    例如带绝对路径

    //shows/show[film=//films/film[tag=current()/@id]/@id]
    

    变成

    /myroot/somepath/shows/show[film=/myroot/somepath/films/film[tag=current()/@id]/@id]
    

    或者如果节目和电影是相对于当前节点的

    ./relativexpath/shows/show[film=./relativexpath/somepath/films/film[tag=current()/@id]/@id]
    

    【讨论】:

    • 谢谢,我现在就试试(+1 建议)。我没有输入的正式模式,但我可以发布一个示例。
    【解决方案2】:

    nonnb 的回答很可能指出了问题所在,但并不是真正的有效解决方案(“更便宜”的轴更好,但仅此一项并不能提高速度,例如索引数据时)。

    请注意,最大的问题是 XPath 表达式谓词在每次评估时都会对树进行另一次完整遍历。你应该用钥匙来做这样的事情;这将(在大多数甚至所有 XSLT 实现中)使索引查找成为可能,从而大大减少了运行时间。

    按 id 定义电影、组和节目的键:

    <xsl:key name="filmByTag" match="film" use="tag" />
    <xsl:key name="groupsByFilm" match="group" use="tag" />
    <xsl:key name="showsByFilm" match="show" use="film" />
    <xsl:key name="showsByGroup" match="show" use="group" />
    

    然后像这样使用它(未经测试,但你应该明白):

    <xsl:variable name="films" select="key('filmByTag', @id)/@id" />
    <xsl:apply-templates select="key('showsByFilm', $films)/@id|key('showsByGroups', key('groupsByFilm', $films)/@id)/@id">
    

    【讨论】:

      【解决方案3】:

      您的 XPath 表达式似乎在进行三向连接,因此除非对其进行优化,否则源文档大小的性能可能为 O(n^3)。优化涉及通过索引查找替换文档的串行搜索。有两种方法可以实现这一点:您可以通过调用 key() 函数(如 Dimitre 所示)替换过滤器表达式来手动优化它,或者您可以使用优化的 XSLT 处理器,例如 Saxon-EE,它应该自动执行相同的优化。

      【讨论】:

        【解决方案4】:

        使用xsl:key 定义一个键,然后使用key 函数进行交叉引用,而不是您当前的比较。向我们展示一个 XML 示例,以便我们了解它的结构,然后我们可以帮助编写具体的代码。

        【讨论】:

        • 谢谢马丁。我已将示例添加到问题中
        【解决方案5】:

        这里有两个完整的解决方案,应该表现出更好的性能:

        请注意:只有在足够大的输入样本上才能获得更好的性能。对于小的输入样本,不值得优化。

        我。不使用//(但不使用键)

        <xsl:stylesheet version="1.0"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
         <xsl:output omit-xml-declaration="yes" indent="yes"/>
         <xsl:strip-space elements="*"/>
        
         <xsl:variable name="vFilms" select="/*/films/film"/>
         <xsl:variable name="vShows" select="/*/shows/show"/>
         <xsl:variable name="vGroups" select="/*/groups/group"/>
         <xsl:variable name="vTags" select="/*/tags/tag"/>
        
         <xsl:template match="/*">
             <xsl:for-each select="$vTags">
            <xsl:apply-templates select=
             "$vShows
                      [film
                      =
                       $vFilms
                          [tag=current()/@id]
                                 /@id
        
                      or
                        group
                        =
                         $vGroups
                                  [film
                                  =
                                   $vFilms
                                      [tag=current()/@id]
                                               /@id
                                   ]
                                    /@id
                       ]
              ">
                <xsl:sort select="date" data-type="text" order="ascending"/>
                <xsl:sort select="time" data-type="text" order="ascending"/>
                </xsl:apply-templates>
           </xsl:for-each>
         </xsl:template>
        
         <xsl:template match="show">
            <xsl:copy-of select="."/>
         </xsl:template>
        </xsl:stylesheet>
        

        二。使用按键

        <xsl:stylesheet version="1.0"
         xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
         <xsl:output omit-xml-declaration="yes" indent="yes"/>
         <xsl:strip-space elements="*"/>
        
         <xsl:key name="kShowByFilmId" match="show"
                  use="film"/>
        
         <xsl:key name="kShowByGroupId" match="show"
                  use="group"/>
        
         <xsl:key name="kGroupByFilmId" match="group"
                  use="film"/>
        
         <xsl:key name="kFilmByTag" match="film"
                  use="tag"/>
        
         <xsl:variable name="vTags" select="/*/tags/tag"/>
        
         <xsl:template match="/*">
             <xsl:for-each select="$vTags">
        
            <xsl:apply-templates select=
             "key('kShowByFilmId',
                  key('kFilmByTag', current()/@id)/@id
                  )
             |
              key('kShowByGroupId',
                  key('kGroupByFilmId',
                      key('kFilmByTag', current()/@id)/@id
                      )
                       /@id
                 )
             ">
                <xsl:sort select="date" data-type="text" order="ascending"/>
                <xsl:sort select="time" data-type="text" order="ascending"/>
            </xsl:apply-templates>
           </xsl:for-each>
         </xsl:template>
        
         <xsl:template match="show">
            <xsl:copy-of select="."/>
         </xsl:template>
        </xsl:stylesheet>
        

        【讨论】:

        • 我相信你在II中的代码,。不正确,您将节目组 ID 与电影 ID 进行匹配(缺少通过组的间接)。
        • @Lucero:很好,谢谢。我相信我的编辑解决了这个问题。 :)
        • 是的,我认为应该这样做(这部分现在与我的答案几乎相同)。
        • @Lucero:正如你所见,我无意复制你的答案 :) 每当两个人独立产生或多或少相同的解决方案时,这很好!
        • 当然,我的评论是为了确认我也相信这个解决方案是正确的。 ;)
        猜你喜欢
        • 1970-01-01
        • 2011-01-09
        • 1970-01-01
        • 2018-09-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-07-24
        • 1970-01-01
        相关资源
        最近更新 更多