1

我已经测量了使用 Jaxb2 将 XML 解组为对象的时间成本,该对象使用带有较长(48 个字符)标签名称的大型(1.7mb)XML 有效负载。我通过在采样模式下运行的 JProfiler 观察到字符串实习工作是所花费时间的重要组成部分。

我做了一些研究,发现 Jaxb 可以在不实习字符串的模式下运行。我的理论是,在某些情况下,在解组期间不暂留字符串可能会以使用更多堆内存为代价来提高性能,因为在暂留过程中不必对每个标签名称字符串进行哈希处理。

我用来抑制 Jaxb 的实习行为的方法是在我的 Fastinfoset“StAXDocumentParser”(实现 XMLStreamReader)上设置“org.codehaus.stax2.internNames”和“org.codehaus.stax2.internNsUris”属性。我不是 100% 清楚为什么必须将这些设置为“true”以防止 Jaxb 对字符串进行实习,但这就是它的工作原理。

这些 JUnit 驱动的测试是我过去得出的结论,即禁用 Jaxb 的字符串实习行为会产生很大的性能差异:

https://github.com/gjd6640/fastinfoset-performance-evaluation

所以我的问题是多方面的:

1)我是否误解了一些重要的事情,不应该首先尝试禁用 Jaxb 的字符串实习行为?

2)有没有更好的方法来指导 Jaxb 不实习字符串?“StAXManager”类不允许您设置这些面向 Woodstox 的属性。对于这个测试,我最终扩展了 StAXManager,如下所示来解决这个问题。这是我不希望在生产中使用的 hack。我怀疑这里的想法是,当 Jaxb 从 Woodstox 流中解组时,它会查看 Woodstox 是否已经在进行实习,并且当“是”时,Jaxb 通过禁用该过程的步骤来做出反应。我通过在 Jaxb 库中捎带该逻辑来作弊,所以想要一个更好的方法来解决这个问题。

package com.sun.xml.fastinfoset.stax;
public class JaxbStringInternSuppressionStaxManager extends StAXManager {
    public JaxbStringInternSuppressionStaxManager() {
        // Add to the allowable list of feature names so that the user may set these "StAXInputFactory" properties
        super.features.put("org.codehaus.stax2.internNames", null);
        super.features.put("org.codehaus.stax2.internNsUris", null);
    }
}

更新:

像往常一样,“一个很好的问题得到了一半的回答”。我刚刚在起草这个问题时注意到“com.sun.xml.internal.bind.v2.runtime.unmarshaller.StAXStreamConnector”检查类“com.sun.xml.internal .fastinfoset.stax.StAXDocumentParser ”是否是可从您正在使用的 XMLStreamReader 分配,如果是这样,则不启用字符串实习。在我的情况下,我的流对象是“com.sun.xml.fastinfoset.stax.StAXDocumentParser”,所以实习不会被禁用。现在的问题是“为什么它只是为了 Fastinfoset 库的内部风格才这样做?” 或许我会仔细阅读这篇文章找到答案。

此外,如果有更好的论坛来解决此类问题,例如活跃的开发人员用户组,请分享该信息,我会看到将它们链接到这篇文章,以便合适的人看到这个问题。

4

2 回答 2

1

我不一定会信任分析器或测试,而无需在有和没有实习的情况下测量实际用例,所以有点怀疑。但是,实习生也有一些问题。特别是它使用固定大小的池大小,因此当池已满时,哈希查找的持续性能会降低到搜索链表。有关更长的讨论,请参阅http://java-performance.info/string-intern-in-java-6-7-8/

简而言之,您可以尝试更改池大小-XX:StringTableSize=n(理想情况下,n 应该是素数),看看会发生什么。

用于-XX:+PrintStringTableStatistics查看程序终止时池的使用情况并尝试不同的大小。

编辑:这是试图回答“有没有更好的方法”(即让实习生更快)。我会把另一个问题留给更有资格的人。

于 2017-12-04T21:09:52.993 回答
0

解决方案选项 1:将整个应用程序交换到不同的 jaxb 实现的简单方法

拉入 jaxb-impl 以使用在此 Fastinfoset 库中性能更好的 Jaxb 版本:

<!-- Both of these libs must be here in order to get performant behavior out of Jaxb by default.
-->
<dependency>
        <groupId>com.sun.xml.fastinfoset</groupId>
        <artifactId>FastInfoset</artifactId>
        <version>1.2.13</version>
        <scope>compile</scope>
</dependency>
<dependency> <!-- This artifactId also exists under javax.xml.bind but it appears that nobody uses that one... -->
    <groupId>javax.xml</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.1</version>
    <scope>runtime</scope>
</dependency>
<!-- End: Both of these libs... -->

这将产生更新其余代码使用的 jaxb 版本的副作用。在某些情况下可能并不理想。例如,如果您正在创建一个需要在各种应用程序中使用的共享库,那么当他们拉入您的共享组件时去更改此功能是不礼貌的。

解决方案选项 2:使用 JVM 的 jaxb 实现和性能黑客来欺骗它相信字符串已经被实习(实现更复杂)

  • 使用“maven-shade-plugin”对 Fastinfoset 库的类进行遮蔽和重新打包。结果应该是一个无逻辑的 Maven 组件。这是可选的,旨在确保使用您的 Fastinfoset 编解码器组件的人不会由于编解码器库引入的传递依赖关系而发生类路径冲突。
  • 创建一个 my-fastinfoset-codec 库,该库提供一个简单的 API 来编码和解码 Fastinfoset 有效负载(考虑使用 InputStreams 和 OutputStreams 作为参数,使用 XMLStreamReader 作为解码器的返回类型)。添加对重新打包的 Fastinfoset 库的依赖项。请注意,如果您使用 Eclipse,当 m2e 的“工作区分辨率”启用时,它不能很好地处理阴影库,因此请为您的编解码器项目禁用它。
  • 将扩展重新打包的 Fastinfoset 库的“StAXManager”的类添加到 my-fastinfoset-codec。这个类应该有助于设置属性,告诉 jaxb 给定它的 XMLStreamReader 已经嵌入了 NS 和标记名称字符串。示例如下:
    包 myrepackagedfastinfosetclassespackageprefix.shaded.com.sun.xml.fastinfoset.stax;
    导入 myrepackagedfastinfosetclassespackageprefix.shaded.com.sun.xml.fastinfoset.stax.StAXManager;
    公共类 JaxbStringInternSuppressionStaxManager 扩展 StAXManager {
        公共 JaxbStringInternSuppressionStaxManager() {
            // 添加到允许的功能名称列表中,以便用户可以设置这些“StAXInputFactory”属性
            super.features.put("org.codehaus.stax2.internNames", null);
            super.features.put("org.codehaus.stax2.internNsUris", null);
        }

        /**
         * 这是一个优化。默认情况下,FastInfoset 库已经实习过字符串和 JVM 的 jaxb 实现
         * 不必要地重复该工作。至少对于 64 位版本的 jdk1.8.0_121 是这样。
         *
         * 此变通办法的工作方式是为 Woodstox 解析器搭载 Jaxb 优化。当我们设置
         * 这些属性告诉 jaxb Woodstox 已经将导致其禁用的字符串
         * 字符串实习。
         *
         * 我们确实探索了将 Maven“javax.xml:jaxb-impl”工件作为依赖项而不是使用
         * JVM 的 jaxb 库。与 FastInfoset 库一起使用时,该外部 jaxb 库的性能确实要好得多
         * 比 JVM 快,但不如禁用实习的 JVM 快 100%。我们放弃探索该解决方案的关键原因
         * 是当您重新打包(通过 maven-shade-plugin)jaxb 库时,它们不再适用于我们的标准 jaxb 绑定
         * maven 组件由于“if (instanceof my_repackaging_project.shaded.XMLElement)”之类的语句
         * 在数据映射过程中使用。
         */
        公共 JaxbStringInternSuppressionStaxManager enableTrickToStopJaxbFromInterningStrings() {
            super.setProperty("org.codehaus.stax2.internNames", true);
            super.setProperty("org.codehaus.stax2.internNsUris", true);
            返回这个;
        }
    }

解决方案选项 3:与 Oracle 签订了 JVM 支持合同的足够多的人提出请求,要求提供某种非内部的 fastinfoset 支持。

我希望 Oracle 教 JVM 提供的 jaxb 实现从给定的 XMLStreamReader 确定此 Fastinfoset 实现配置为实习字符串是相当简单的。

没有成功的解决方案可能性:从上面的解决方案 1 中重新打包两个罐子

可以使用“maven-shade-plugin”或类似方法来创建带有自定义前缀包名的新 jar。经过一番摆弄,这确实适用于这些库。但是,我得出的最终结果是,重新打包的 jaxb 库现在希望 jaxb-RI 生成的 OXM 对象具有来自新的阴影包名称的注释。我的是以标准方式构建的,因此我重新打包的解决方案不会将任何数据映射到我的对象。我不愿意规定我们的 OXM 绑定库使用重新打包的 jaxb 库,我也不喜欢这种方法来探索更仔细地重新打包的方法,以免更改用于这些注释的包。

我没有探索的解决方案选项:

使用具有“.internal”的 JVM 的 fastinfoset 类。在他们的包名中。这些可能会在 JVM 附带的 jaxb 实现中表现良好,但我拒绝将“未来的我”暴露给使用内部 api 带来的支持成本。

于 2017-12-05T16:51:31.493 回答