使用函数时如何改善eXSLT性能问题

How to improve eXSLT performance problems when using functions

TL;DR:

似乎 运行ing eXSLT 比它在 XSLT2 中的对应物慢得多。 (7 分钟对 18 小时)

下面我将解释我的问题,写下同一转换在 eXSLT 和 XSLT2 中的两种实现。

当然,引擎是不同的,至于 XSLT2 我使用 SaxonHE,而对于 eXSLT 我使用 python 和 lxml。

最后我寻求帮助以提高 eXSLT 部分的速度,因为我更愿意使用 python 而不是 Java。


我必须将一个大的(~200k 层 1 元素)XML 转换为 csv。

我有 2 个实现:

由于在编写 CSV 时,即使元素没有值也必须打印分隔符,我采用了这种方法:

我创建了 2 个函数:

myf:printElement 接收一个元素和一个表示元素为空时必须写入的分隔符数量的数字。

myf:printAttr 接收一个属性,并打印它加上分隔符。

如果我也定义分隔符为:

<xsl:param name="delim" select="','"/>

函数在每个文件中声明如下:

XSLT2

<!-- Shortcut function to print an attribute plus a delimiter -->
<xsl:function name="myf:printAttr" as="xs:string">
    <xsl:param name="pAttr" as="attribute()*"/>
    <xsl:value-of select="concat($pAttr,$delim)"/>
</xsl:function>

<!-- This function will call the apply templates if the given elements exist. Else, it will return as many delimiters as the number given as second parameter -->
<xsl:function name="myf:printElement" as="item()*">
    <xsl:param name="pElement" as="element()*"/>
    <xsl:param name="pCount" as="xs:integer"/>
    <xsl:choose>
        <xsl:when test="$pElement">
            <xsl:apply-templates select="$pElement"/>
        </xsl:when>
        <xsl:otherwise>
            <!-- explicit void separator or will add an space -->
            <xsl:value-of select="for $i in 1 to $pCount return $delim" separator=""/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:function>

eXSLT

<!-- Shortcut function to print an attribute plus a delimiter -->
<func:function name="myf:printAttr">
    <xsl:param name="pAttr"/>
    <func:result select="concat($pAttr,$delim)"/>
</func:function>
<!-- This function will call the apply templates if the given elements exist. Else, it will return as many delimiters as the number given as second parameter -->
<func:function name="myf:printElement" as="item()*">
    <xsl:param name="pElement" as="element()*"/>
    <xsl:param name="pCount" as="xs:integer"/>
    <xsl:choose>
        <xsl:when test="$pElement">
            <func:result>
                <xsl:apply-templates select="$pElement"/>
            </func:result>
        </xsl:when>
        <xsl:otherwise>
            <!-- explicit void separator or will add an space -->
            <func:result select="str:padding($pCount,$delim)"/>
        </xsl:otherwise>
    </xsl:choose>
</func:function>

其余文件同理

所以,假设我有一个像这样的 XML:

<root>
  <Tier1 attr1="A" attr2="B"/>
  <Tier1 attr1="C" attr2="D">
    <Child2 type="1" val="ABC"/>
    <Child2 type="3" val="123"/>
  </Tier1>
  <Tier1 attr1="E" attr2="F">
    <Child2 type="2" val="pancakes"/>
    <Child2 type="1" val="42"/>
    <Child3 a="H">
        <Child4 Month="JUN"/>
    </Child3>
  </Tier1>
</root>

有:

<xsl:param name="break" select="'&#xA;'"/>
<xsl:template match="/">
     <xsl:apply-templates select="root/Tier1"/>`
</xsl:template>
<xsl:template match="Tier1">
    <xsl:value-of select="myf:printAttr(@attr1)"/>
    <xsl:value-of select="myf:printAttr(@attr2)"/>
    <xsl:value-of select="myf:printAttr(Child2[@type='1']/@val)"/>
    <xsl:value-of select="myf:printAttr(Child2[@type='2']/@val)"/>
    <xsl:value-of select="myf:printAttr(Child2[@type='3']/@val)"/>
    <xsl:apply-templates/>
    <!-- line break after each Tier1 -->
    <xsl:if test="following-sibling::*">
        <xsl:value-of select="$break"/>
    </xsl:if>
</xsl:template>
<xsl:template match="Child3">
    <xsl:value-of select="myf:printAttr(@a)"/>
    <xsl:value-of select="ama:printElement(Child4,3)"/>
</xsl:template>
<xsl:template match="Child4">
    <xsl:value-of select="myf:printAttr(@Day)"/>
    <xsl:value-of select="myf:printAttr(@Month)"/>
    <!-- We dont want comma after last element-->
    <xsl:value-of select=@Average/>
</xsl:template>

我会得到想要的 csv 输出:

T1_attr1, T1_attr2, C2_t1, C2_t2, C2_t3, C3_a, C4_Mont, C4_Day, C4_Average
A,B,,,,,,,
C,D,ABC,,123,,,,
E,F,42,pancakes,,H,JUN,3,1200

关于上面的一些注释:

所以,现在,问题是:

正如我在开始时所说,我必须 运行 转换为一个包含超过 200k Tier1 元素的巨大文件。

两个变换 script/program 做同样的事情:

  1. 打开文件
  2. 打开 XSLT
  3. 后者为前者
  4. 保存结果

我知道我在谈论转换引擎的不同实现,但这种差异太显着了,不可能是因为这个。 测试同一引擎的唯一方法是在 Saxon-PE 或 Saxon-EE 下使用 eXSLT,因为它在 Saxon-HE 中不可用。 当然,python 中没有 XSLT2 实现。

我想知道为什么 python 版本耗时太长。这是使用 eXSLT 所固有的吗?或 有什么方法可以改进吗?

当然这是例子XML,真正的要多很多元素,确实比较复杂

这是一个更大项目的一部分,我不想只为此依赖 JVM,但是,差异如此之大,以至于现在 Python 不是一个选项。

谢谢!

在我看来,您似乎过度设计了这个问题。

以下简单的 XSLT 1.0 转换

<xsl:transform version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" encoding="utf-8" />

  <xsl:template match="/root">
    <xsl:text>T1_attr1,T1_attr2,C2_t1,C2_t2,C2_t3,C3_a,C4_Month,C4_Day,C4_Average</xsl:text>
    <xsl:apply-templates select="Tier1" />
  </xsl:template>

  <xsl:template match="Tier1">
    <xsl:text>&#xA;</xsl:text>
    <xsl:value-of select="@attr1" />                   <xsl:text>,</xsl:text>
    <xsl:value-of select="@attr2" />                   <xsl:text>,</xsl:text>
    <xsl:value-of select="Child2[@type = '1']/@val" /> <xsl:text>,</xsl:text>
    <xsl:value-of select="Child2[@type = '2']/@val" /> <xsl:text>,</xsl:text>
    <xsl:value-of select="Child2[@type = '3']/@val" /> <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/@a" />                <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/Child4/@Month" />     <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/Child4/@Day" />       <xsl:text>,</xsl:text>
    <xsl:value-of select="Child3/Child4/@Average" />
  </xsl:template>
</xsl:transform>

应用于

<root>
  <Tier1 attr1="A" attr2="B">
  </Tier1>
  <Tier1 attr1="C" attr2="D">
    <Child2 type="1" val="ABC" />
    <Child2 type="3" val="123" />
  </Tier1>
  <Tier1 attr1="E" attr2="F">
    <Child2 type="2" val="pancakes" />
    <Child2 type="1" val="42" />
    <Child3 a="H">
        <Child4 Month="JUN" Day="3" Average="1200" />
    </Child3>
  </Tier1>
</root>

产生

T1_attr1,T1_attr2,C2_t1,C2_t2,C2_t3,C3_a,C4_Month,C4_Day,C4_Average
A,B,,,,,,,
C,D,ABC,,123,,,,
E,F,42,pancakes,,H,JUN,3,1200