使用函数时如何改善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 个实现:
- 一个用的是python,下面是libxml,我用的是eXSL。
- 另一个使用 SaxonHE,因此我对其使用 XSL2 转换。
由于在编写 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="'
'"/>
<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
关于上面的一些注释:
Child2 可以在 Tier1 下重复,但只有一组给定的类型值,不能重复。
此外,元素内没有文本,这使得使用 2 个函数的这种方法涵盖了我可能遇到的所有情况。尽管 printAttr 可能也适用于文本节点。
我添加了列名以便于阅读。在代码中,我在开始时添加了它,一个使用 eXSLT 设置的内部节点,一个使用 XSLT2.
的简单字符串数组
所以,现在,问题是:
正如我在开始时所说,我必须 运行 转换为一个包含超过 200k Tier1 元素的巨大文件。
- 使用 SaxonHE 需要 7 分钟
- 使用Python,需要18小时
两个变换 script/program 做同样的事情:
- 打开文件
- 打开 XSLT
- 后者为前者
- 保存结果
我知道我在谈论转换引擎的不同实现,但这种差异太显着了,不可能是因为这个。
测试同一引擎的唯一方法是在 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>
</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
TL;DR:
似乎 运行ing eXSLT 比它在 XSLT2 中的对应物慢得多。 (7 分钟对 18 小时)
下面我将解释我的问题,写下同一转换在 eXSLT 和 XSLT2 中的两种实现。
当然,引擎是不同的,至于 XSLT2 我使用 SaxonHE,而对于 eXSLT 我使用 python 和 lxml。
最后我寻求帮助以提高 eXSLT 部分的速度,因为我更愿意使用 python 而不是 Java。
我必须将一个大的(~200k 层 1 元素)XML 转换为 csv。
我有 2 个实现:
- 一个用的是python,下面是libxml,我用的是eXSL。
- 另一个使用 SaxonHE,因此我对其使用 XSL2 转换。
由于在编写 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="'
'"/>
<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
关于上面的一些注释:
Child2 可以在 Tier1 下重复,但只有一组给定的类型值,不能重复。
此外,元素内没有文本,这使得使用 2 个函数的这种方法涵盖了我可能遇到的所有情况。尽管 printAttr 可能也适用于文本节点。
我添加了列名以便于阅读。在代码中,我在开始时添加了它,一个使用 eXSLT 设置的内部节点,一个使用 XSLT2.
的简单字符串数组
所以,现在,问题是:
正如我在开始时所说,我必须 运行 转换为一个包含超过 200k Tier1 元素的巨大文件。
- 使用 SaxonHE 需要 7 分钟
- 使用Python,需要18小时
两个变换 script/program 做同样的事情:
- 打开文件
- 打开 XSLT
- 后者为前者
- 保存结果
我知道我在谈论转换引擎的不同实现,但这种差异太显着了,不可能是因为这个。 测试同一引擎的唯一方法是在 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>
</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