(XSLT)为什么Saxon-HE 9.8处理器在我的情况下比dotNet处理器慢?

时间:2018-01-19 08:05:02

标签: c# xml xslt saxon

(注意:这篇文章主要通过回复建议进行了编辑) 我目前在dotNet处理器和Saxon-HE 9.8处理器中运行几乎相同的xslt,但我发现Saxon(2.2秒)比dotNet(0.03秒)慢得多。那我怎么修好呢?这是我的【简化】xml样本,只需复制<A><Z>并反复粘贴50次,我认为大小只有10kb左右:

<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<?xml-stylesheet type="text/xsl" href="OutputFile.xslt"?>
  <Header>
      <A><![CDATA[NOTHING]]></A>
      <B><![CDATA[NOTHING]]></B>
      <C><![CDATA[NOTHING]]></C>
    <X>_R_testXR12</X>
    <Y>_R_testYR12</Y>
    <Z>_R_testZR12</Z>
  </Header>

以下是我的代码:

Saxon C#

var processor = new Processor();
var compiler = processor.NewXsltCompiler();
var executable = compiler.Compile(new Uri(xslt.FullName));
var transformer = executable.Load30();
var serializer = new Serializer();

FileStream outStream = new FileStream(output.ToString(), FileMode.Create, FileAccess.Write);
serializer.SetOutputStream(outStream);

using (var inputStream = input.OpenRead())
{

    /*timer start*/
    var watch = Stopwatch.StartNew();
    transformer.ApplyTemplates(inputStream, serializer);
    /*timer end*/
    watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Console.WriteLine(elapsedMs); Console.Read();
    outStream.Close();
}

Saxon XSLT

    <!--  Saxon  in xslt-->
<xsl:stylesheet version="3.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" indent="yes"/>
  <xsl:template match="/">
    <xsl:variable name="NodesExtraCRI" select="/Header/*[( starts-with(text(), '_R_testZR'))]"></xsl:variable>
        <xsl:for-each select = "$NodesExtraCRI">
          <xsl:sort select = "text()" data-type = "number" order = "ascending"/>
          <xsl:value-of select="text()"/>
        </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

.NET C#

// Enable XSLT debugging.  
XslCompiledTransform xslt = new XslCompiledTransform(true);

// Compile the style sheet.  
xslt.Load(stylesheet);

// Execute the XSLT transform. 
/*timer start*/
var watch = System.Diagnostics.Stopwatch.StartNew();
FileStream outputStream = new FileStream(outputFile, FileMode.Append);
xslt.Transform(sourceFile, null, outputStream);
/*timer end*/
watch.Stop(); var elapsedMs = watch.ElapsedMilliseconds; Console.WriteLine(elapsedMs); Console.Read();

.NET XSLT

<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output method="text" indent="yes"/>
  <xsl:template match="/">
    <xsl:variable name="NodesExtraCRI" select="/Header/*[( starts-with(text(), '_R_testZR'))]"></xsl:variable>
      <xsl:for-each select = "$NodesExtraCRI">
        <xsl:sort select = "text()" data-type = "number" order = "ascending"/>
        <xsl:value-of select="text()"/>
      </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>

3 个答案:

答案 0 :(得分:2)

Saxon的.NET版本通常比Java版本慢3-5倍,尽管付出了艰苦的努力,但我们实际上并不知道为什么。有一段时间它只慢了25%。

我怀疑你在这里看到的重大差异主要是初始化成本。虽然你的计时器只测量转换时间(包括源文件解析和序列化但不包括样式表编译),但你只运行转换一次,如果你运行20次,看看数字是否不同会很有趣。取平均值。

你还没有说过源文件大小是什么,但除非是数百兆字节,否则2.2s的时间似乎相当过分,绝对值得寻找解释。

我注意到<xsl:for-each select="1 to 10">循环的主体不依赖于上下文项,因此这里有一个“循环提升”优化的范围,它只执行一次主体然后复制结果。 Saxon-HE并没有尝试这样的优化,但Saxon-EE确实如此,微软处理器也很有可能。但这还不足以说明这种差异。

所以,我建议以下实验来收集更多见解:

(a)从C#级别重复运行变换以获得平均时间。

(b)通过在循环体中做某事来消除循环提升优化的可能性,这意味着每次结果都不同

(c)了解转换时间如何随源文档大小而变化

(d)进行一些剖析以查看热点的位置。

答案 1 :(得分:1)

我试图重现你说的极端差异,但我无法做到。

由于我无法理解所提供的样式表应该做什么,特别是xsl:sort data-type = "number"之前选择的元素_R_testZR<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:math="http://www.w3.org/2005/xpath-functions/math" exclude-result-prefixes="xs math" version="3.0"> <xsl:output indent="yes"/> <xsl:template match="/*"> <xsl:copy> <xsl:for-each select="*[starts-with(., 'test_Z')]"> <xsl:sort select="@pos" data-type="number"/> <xsl:copy-of select="."/> </xsl:for-each> </xsl:copy> </xsl:template> </xsl:stylesheet> 开头(即字母和不是数字)似乎毫无意义,我首先写了一些样式表来创建一些要排序的样本数据。

然后我创建了一个样式表来对生成的样本数据进行排序:

-t -repeat

我首先使用Saxon 9.8.0.7 HE Java和.NET以及 static void RunSaxon(string xmlUrl, string xslUrl, string outputFile) { Processor processor = new Processor(); Xslt30Transformer xslt30Transformer = processor.NewXsltCompiler().Compile(new Uri(xslUrl)).Load30(); using (Stream resultStream = File.OpenWrite(outputFile)) { using (Stream inputStream = File.OpenRead(xmlUrl)) { xslt30Transformer.ApplyTemplates(inputStream, processor.NewSerializer(resultStream)); } } } 选项从命令行运行该样式表,以了解是否确实需要几秒钟。但是,Java版本已报告

  

过去11次运行的平均执行时间:48.161523ms

和.NET版

  

过去11次运行的平均执行时间:79.13861ms

所以.NET版本(已知)比Java版本慢得多,但它至少在我的机器上需要毫秒而不是秒。

此外,我在Visual Studio 2017中编写了一些C#控制台应用程序代码,使用类似

之类的代码对同一样本执行相同的样式表
ApplyTemplates()

并使用Profiler in VS来分析该代码,结果显示大量的CPU使用量甚至不会来自Compile调用,而是主要用途是{{1} }方法(接近64%)后跟new Processor()构造函数(接近28.5%):

Function Name   Total CPU (ms)  Total CPU (%)   Self CPU (ms)   Self CPU (%)    Module
Saxon98HEPerfTest.exe (PID: 17768)  718 100,00 %    718 100,00 %    Saxon98HEPerfTest.exe
  Saxon98HEPerfTest.Program::Main   680 94,71 % 0   0,00 %  Saxon98HEPerfTest.exe
  Saxon98HEPerfTest.Program::RunSaxon   677 94,29 % 0   0,00 %  Saxon98HEPerfTest.exe
  [External Call] Saxon.Api.XsltCompiler::Compile   458 63,79 % 458 63,79 % Multiple modules
  [External Call] Saxon.Api.Processor::.ctor    204 28,41 % 204 28,41 % Multiple modules
  [External Call] Saxon.Api.Processor::NewXsltCompiler  5   0,70 %  5   0,70 %  Multiple modules

通过在RunSaxon方法中设置断点获得的一些更详细的数据:

Function Name   Total CPU (ms)  Total CPU (%)   Self CPU (ms)   Self CPU (%)    Module
Saxon.Api.XsltCompiler::Compile 683 61,20 % 683 61,20 % Multiple modules
Saxon.Api.Processor::.ctor  214 19,18 % 214 19,18 % Multiple modules
Saxon.Api.Xslt30Transformer::ApplyTemplates 181 16,22 % 181 16,22 % Multiple modules
Saxon.Api.Processor::NewXsltCompiler    5   0,45 %  5   0,45 %  Multiple modules
Saxon.Api.XsltExecutable::Load30    3   0,27 %  3   0,27 %  Multiple modules
Saxon.Api.Processor::NewSerializer  1   0,09 %  1   0,09 %  Multiple modules
System.IO.File.OpenWrite(System.String)$##60017A3   1   0,09 %  1   0,09 %  Multiple modules

我还尝试使用秒表来测量ApplyTemplates()来电,正如您尝试的那样:

    static void MeasureSaxon(string xmlUrl, string xslUrl, string outputFile)
    {
        Processor processor = new Processor();

        Xslt30Transformer xslt30Transformer = processor.NewXsltCompiler().Compile(new Uri(xslUrl)).Load30();

        Stopwatch watch = new Stopwatch();

        using (Stream resultStream = File.OpenWrite(outputFile))
        {
            using (Stream inputStream = File.OpenRead(xmlUrl))
            {            
                watch.Start();
                xslt30Transformer.ApplyTemplates(inputStream, processor.NewSerializer(resultStream));
                watch.Stop();
            }
        }
        Console.WriteLine("{0} ms", watch.ElapsedMilliseconds);
    }

我得到的结果大约是165毫秒。 XslCompiledTransform确实更快,大约32毫秒,用

测量
    private static void RunXslCompiledTransform(string xmlUrl, string xslUrl, string outputFile)
    {
        XslCompiledTransform processor = new XslCompiledTransform();
        processor.Load(xslUrl);

        Stopwatch watch = new Stopwatch();
        watch.Start();
        processor.Transform(xmlUrl, outputFile);
        watch.Stop();
        Console.WriteLine("{0} ms", watch.ElapsedMilliseconds);
    }

但是我没有像你似乎经历的那样发生如此极端的差异。

以下是生成样本数据的样式表(由于random-number-generator()使用而需要Saxon PE或EE):

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:math="http://www.w3.org/2005/xpath-functions/math"
    xmlns:map="http://www.w3.org/2005/xpath-functions/map"
    xmlns:array="http://www.w3.org/2005/xpath-functions/array"
    exclude-result-prefixes="xs math map array"
    expand-text="yes"
    version="3.0">

    <xsl:output method="xml" indent="yes"/>

    <xsl:param name="seed" as="xs:dateTime" select="current-dateTime()"/>

    <xsl:param name="items" as="xs:integer" select="5000"/>

    <xsl:template match="/" name="xsl:initial-template">
        <root>
            <xsl:for-each select="random-number-generator($seed)?permute(1 to $items)">
                <A>...</A>
                <B>...</B>
                <C>...</C>
                <X>test_Y</X>
                <Y>test_Y</Y>
                <Z pos="{.}">test_Z_{format-number(., '0000')}</Z>
            </xsl:for-each>
        </root>
    </xsl:template>

</xsl:stylesheet>

由于生成样本数据的样式表需要Saxon PE或EE,我已将生成的样本输入上传到https://martin-honnen.github.io/xslt/2018/test2018012001Input5000.xml

答案 2 :(得分:0)

我还尝试运行您提供的代码段。

以下是我的结果:

Saxon .Net版本:59 ms(编译时间28 ms,运行时间31 ms)

.Net版本:3毫秒

我们知道Saxon在.NET上速度较慢,但​​我没有看到报告的2-3秒速度。