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

(XSLT) why Saxon-HE 9.8 processor slower then dotNet processor in my case?

(注意:此 post 主要通过回复建议进行了编辑) 我目前 运行 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>

这是我的代码:

撒克逊 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();
}

撒克逊 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>

Saxon 的 .NET 版本通常比 Java 版本慢 3-5 倍,尽管付出了巨大的努力,但我们实际上并不知道原因。有一次它只慢了 25%。

我怀疑您在这里看到的最大差异主要是初始化成本。虽然您的计时器只测量转换时间(包括源文档解析和序列化但不包括样式表编译),但您只 运行 转换一次并且如果您 运行 说20次取平均值

你没说源文件的大小是多少,不过除非是几百兆,否则这里2.2s的时间似乎有点过分了,绝对值得找一个解释。

我注意到 <xsl:for-each select="1 to 10"> 循环的主体不依赖于上下文项,因此这里有 "loop lifting" 优化的范围,它只执行主体一次,然后复制结果. Saxon-HE 不会尝试进行此类优化,但 Saxon-EE 会尝试,而且 Microsoft 处理器也很可能会这样做。但这还不足以解释这种差异。

因此,我建议进行以下实验以收集更多见解:

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

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

(c) 查看转换时间如何随源文档大小变化

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

我已经尝试重现你所说的极端差异,但我没能做到。

因为我无法理解所提供的样式表应该做什么,特别是 xsl:sortdata-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>

那个样式表我 运行 首先从命令行使用 Saxon 9.8.0.7 HE Java 和 .NET 以及 -t -repeat 选项来了解它是否真的需要几秒钟.然而,Java版本报告

Average execution time over last 11 runs: 48.161523ms

和 .NET 版本

Average execution time over last 11 runs: 79.13861ms

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

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

的代码对同一样本执行相同的样式表
    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));
            }
        }
    }

并使用 Profiler in VS 分析该代码,结果显示相当多的 CPU 用法甚至不是来自 ApplyTemplates() 调用,而是主要用法是通过 Compile 方法(近 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

我也试过 运行 你提供的代码片段。

这是我的结果:

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

.Net 版本:3 毫秒

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