You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Saxon在XPath基准测试中初始慢后续快的原因及冷执行方案

Saxon XPath冷运行基准测试问题

问题背景

在Groovy/Java环境中对Saxon的XPath表达式做基准测试,每次重新创建Processor、DocumentBuilder和XPathCompiler对象,期望获取稳定的冷运行性能数据。测试代码如下:

static def measureSaxon(String xml, String xpath) {
    Processor processor = new Processor(false);
    processor.setConfigurationProperty(FeatureKeys.OPTIMIZATION_LEVEL, "0")
    XdmNode xdm = processor.newDocumentBuilder().build(new StreamSource(new StringReader(xml)));
    def compiler = processor.newXPathCompiler()
    return measureExecutionTime(
      "Saxon",
      xpath,
      {
          def evaluate = compiler.evaluate(xpath, xdm)
          if (evaluate instanceof XdmValue && evaluate.size() > 0) {
              return evaluate.itemAt(0).getStringValue()
          } else {
              return "No node found"
          }
      }
    )
}

已尝试设置processor.setConfigurationProperty(FeatureKeys.OPTIMIZATION_LEVEL, "0"),但首次调用执行速度仍远慢于后续调用,基准测试结果如下:

# Benchmark Results for Saxon

| XPath Expression                | Execution Time (ns) | Execution Time (ms) | Result               |
|---------------------------------|---------------------|---------------------|----------------------|
| `/program/@name`                | 151562270           | 151.56227           | j$Collections        |
| `/program/objects/o/@base`      | 522288              | 0.522288            | some.class           |
| `/program/objects/o/o/o/o/@base`| 12328695            | 12.328695           | org.lang.bytes       |

疑问:Saxon内部是否存在无法通过重置Processor或设置OPTIMIZATION_LEVEL为0来禁用的深层缓存机制或优化?如何强制Saxon始终以“首次调用”的状态运行,以测量真实的冷执行性能?


问题解答

1. Saxon的深层缓存/初始化机制

是的,Saxon存在一些不受OPTIMIZATION_LEVEL=0控制的底层初始化和缓存逻辑,主要包括:

  • JVM类加载缓存:首次调用时会加载Saxon核心类(如XPath解析器、字节码生成器相关类),后续调用直接复用已加载的类,这是JVM层面的机制,和Saxon自身配置无关。
  • 类级静态缓存:Saxon内部有静态缓存(如命名空间URI映射、内置函数元数据),这些缓存属于类级别,不会随Processor实例销毁而清除。
  • 字节码片段缓存:即使关闭表达式优化,Saxon仍可能缓存生成的小型字节码片段,这类缓存绑定到类加载器而非Processor实例。
  • 文档模型隐式优化:XdmNode构建时会生成节点索引等底层结构优化,属于文档实例本身,不受全局优化级别控制。

2. 强制冷运行的解决方案

要模拟真实的首次调用状态,需要从JVM和Saxon配置两个层面入手:

(1)使用隔离类加载器

每次测试用新的类加载器加载Saxon相关类,彻底规避JVM类加载缓存:

static ClassLoader createIsolatedClassLoader() throws Exception {
    URL[] saxonUrls = [
        new File("path/to/saxon-he.jar").toURI().toURL(),
        // 若有依赖jar需一并加入
    ]
    return new URLClassLoader(saxonUrls, null) // 不继承父类加载器的缓存
}

static def measureSaxonColdRun(String xml, String xpath) throws Exception {
    ClassLoader cl = createIsolatedClassLoader()
    // 反射创建Processor实例
    Class processorClass = cl.loadClass("net.sf.saxon.s9api.Processor")
    Object processor = processorClass.getConstructor(boolean.class).newInstance(false)
    // 设置优化级别
    Class featureKeysClass = cl.loadClass("net.sf.saxon.lib.FeatureKeys")
    processor.getClass().getMethod("setConfigurationProperty", String.class, Object.class)
        .invoke(processor, featureKeysClass.getField("OPTIMIZATION_LEVEL").get(null), "0")
    
    // 构建XdmNode
    Class documentBuilderClass = cl.loadClass("net.sf.saxon.s9api.DocumentBuilder")
    Object builder = processor.getClass().getMethod("newDocumentBuilder").invoke(processor)
    Class streamSourceClass = cl.loadClass("javax.xml.transform.stream.StreamSource")
    Class stringReaderClass = cl.loadClass("java.io.StringReader")
    Object source = streamSourceClass.getConstructor(Reader.class)
        .newInstance(stringReaderClass.getConstructor(String.class).newInstance(xml))
    Object xdm = builder.getClass().getMethod("build", streamSourceClass).invoke(builder, source)
    
    // 创建XPathCompiler并执行
    Class xpathCompilerClass = cl.loadClass("net.sf.saxon.s9api.XPathCompiler")
    Object compiler = processor.getClass().getMethod("newXPathCompiler").invoke(processor)
    
    return measureExecutionTime(
        "Saxon Cold Run",
        xpath,
        {
            Object evaluate = compiler.getClass().getMethod("evaluate", String.class, Object.class)
                .invoke(compiler, xpath, xdm)
            Class xdmValueClass = cl.loadClass("net.sf.saxon.s9api.XdmValue")
            if (xdmValueClass.isInstance(evaluate)) {
                int size = (int) xdmValueClass.getMethod("size").invoke(evaluate)
                if (size > 0) {
                    Object item = xdmValueClass.getMethod("itemAt", int.class).invoke(evaluate, 0)
                    return (String) item.getClass().getMethod("getStringValue").invoke(item)
                }
            }
            return "No node found"
        }
    )
}

(2)禁用Saxon静态缓存

测试前设置系统属性关闭Saxon内部的静态元数据缓存:

System.setProperty("net.sf.saxon.static-cache", "off");

(3)关闭文档索引优化

创建DocumentBuilder时禁用节点索引,避免文档构建时的隐式优化:

DocumentBuilder builder = processor.newDocumentBuilder();
builder.setConfigurationProperty(FeatureKeys.BUILD_TREE_INDEXES, "false");

(4)使用JMH的fork模式

如果用JMH做基准测试,设置每次测试启动新JVM进程,彻底隔离缓存:

@Fork(value = 1, jvmArgs = {"-Dnet.sf.saxon.static-cache=off"})
@Benchmark
public String saxonColdRun() {
    // 测试逻辑
}

3. 额外建议

  • 可将XML文档解析时间从测试中分离,单独测量XPath编译+执行的冷性能。
  • 多次重复冷运行测试并取平均值,消除单次JVM启动的偶然波动。

内容的提问来源于stack exchange,提问作者Volodya Lombrozo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 16:34:53