XSLT:由于复杂的XPath表达式导致性能不佳?

时间:2011-12-11 15:08:56

标签: performance xslt xpath

在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>

说明:

  • 标签是贴在电影上的属性(事实上,它更像是一个类别)。
  • 一组是电影的枚举。
  • 一个节目引用一部电影或一组。
  • 我想要的是:对于每个标签,我正在寻找引用具有当前标签的电影的节目,以及引用至少其中一部电影具有当前标签的组的节目。

5 个答案:

答案 0 :(得分:6)

处理大型文档时,

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

e.g。有绝对路径

//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 :(得分:3)

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">

答案 2 :(得分:2)

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

答案 3 :(得分:1)

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

答案 4 :(得分:1)

以下是两个应该表现出更好性能的完整解决方案:

请注意:仅在足够大的输入样本上才会注册更好的性能。在小输入样本上,优化它是不值得的。

<强>予。不使用// (但不使用密钥)

<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>

<强> II。使用密钥

<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>