Java 中的 XML:数据绑定,第 2 部分:性能
Java 中的 XML:数据绑定,第 2 部分:性能 | ![]() |
![]() |
||
![]() |
||||
![]() |
![]() |
经过了第 1 部分对数据绑定框架的介绍后,现在对其进行测试
Dennis M. Sosnoski(dms@sosnoski.com)
企业 Java 专家 Dennis Sosnoski 研究了 Java 中用于 XML 数据绑定的几种框架的速度和内存使用情况。这些框架包含第 1 部分中讨论的所有代码生成方法、更早的一篇文章中讨论的 Castor 映射绑定方法和一种令人惊讶的有可能成功的新方法。如果您正在您的 Java 应用程序中使用 XML,那么您会希望了解如何将这些数据绑定方法结合在一起! 第 1 部分介绍了有关为什么您希望对 XML 使用数据绑定的背景知识,还概述了可用于数据绑定的 Java 框架。如果您尚未阅读第 1 部分,那么现在您也许至少应该浏览一下那篇文章。在本部分中,我将直接讨论性能问题,而不会进一步讨论原因和方法! 性能测试
注:清单 1 中的机场名称信息通常是一行代码。为了适应列大小,一些代码行被拆开,出现在两行上。 除了紧凑格式外,我还尝试了一个变体,它的数据值更多地使用了子元素(只对 ID 和 IDREF 继续使用属性)。下面是用在此被称为完整格式的格式表示的同一个数据: 清单 2. 完整的文档格式
通常,根据所用文档的大小不同,XML 框架的相对性能会有巨大差异,因此在这些性能测试中,我同时包含了大文档和小文档。大文档(time-comp.xml 和 time-full.xml)使用相同的数据值(分别以如上所示的两种不同格式表示)。因此大小明显不同(紧凑格式的为 106 KB,而完整格式的为 211 KB)。小文档都在集合中,每个集合包含 34 个文档,紧凑格式(ttcomp)的大小从 1.4-3.3 KB 不等,完整格式(ttfull)的大小从 2.2-5.8 KB 不等。与大文档一样,小文档集合中的相应文档包含相同的数据值。可以从下载页面(请参阅参考资料)获得测试中使用的完整文档集。
我更希望这些结果能使用更多的文档变体而不只有两种格式来进行测试。但是,由于需要为代码生成提供 W3C XML Schema(Schema)和文档类型定义(Document Type Definition,DTD)描述,还要为映射版本提供映射文件和基类,因此为数据绑定测试添加更多文档所涉及的工作量是可观的。本文使用的两种格式(包含大文档和小文档变体)至少会相当具有代表性地说明,对于典型的业务文档,数据绑定备用方案是如何执行的。但是,由于这些文档中的大多数数据值可以转换成基本类型 ,所以它们可能会使映射绑定方法显示出,内存使用情况将好于典型的普通文档。这导致一种非常紧凑的内部表示。对于其中大多数数据值都需要保存为 所有测试结果都是使用 1.4GHz Athlon 系统(拥有 256MB DDR RAM,运行 RedHat Linux 7.2)获得的。在所有测试中,我都使用了 Sun 的 JDK 1.4.1 for Linux。所测试的每个数据绑定框架的特定版本如下:JAXB Beta 1、Castor 0.9.4.1、JBind 1.0 Beta 12/07、Quick 4.3.1 和 Zeus Beta 3.5(JiBX 是一个特例 — 请参阅测试结果后面的那么什么是 JiBX?以获取详细信息)。除了 JBind 和 JiBX 之外,所有测试都使用了 Piccolo SAX2 解析器 V1.0.3。这是我知道的最快的 SAX2 解析器,它通常可以达到或超出用于 JiBX 测试的 XMLPull 解析器(XPP3 V1.1.2)的速度。JBind 无法使用 Piccolo 解析器,因此为测试 JBind,我使用了 Xerces Java 2 V2.2.0。 为了提供数据绑定和其它备用方法之间的性能比较,我还只使用 SAX2 解析器对相同文件运行了计时测试,并且使用 dom4j 文档模型(文档模型中的性能佼佼者,它允许使用不同的 SAX2 解析器解析输入文档)运行了计时和内存测试。对于这些测试,我使用了 dom4j V1.3。 在这些计时和内存使用量测试中,我使用的基本框架与以前的文档模型测试(请在参考资料中参阅作者有关文档模型性能的文章。)中所用的相同。这个基准测试框架首先将所有文档读入内存缓冲区,然后对针对文档的输入和输出操作的多次传递进行计时。输入计时和输出计时中显示的测试结果是数次传递过程中的最佳计时。这应当代表了服务器类型的环境(其中重复执行相同的代码)中的长期性能。 输入计时
dom4j 构造文档的内存表示所花费的时间不到单独使用解析器所花费时间的两倍。优于该性能的唯一数据绑定框架是 JiBX。与 dom4j 相比,JAXB、Quick 和 Zeus 都获得了不错的性能数字,但是所花费的时间整体来说都几乎是 JiBX 的两倍。比较起来,Castor 非常缓慢,使用映射绑定和生成代码都如此。 相对于这些测试中的大多数绑定框架,JBind 的执行速度慢了整整一个数量级。这样拙劣的性能一小部分原因是由于用于 JBind 测试的解析器比较慢(因为它无法使用其它测试所用的解析器)。更大的原因可能是由于 JBind 强制在输入时对照 Schema 进行文档验证,这样会增加大量开销。但是,导致这一拙劣性能的最主要原因可能是由于 JBind 框架本身,该框架使用非常间接的方法来进行绑定(在当前实现中,绑定建立在 DOM 文档模型之上)。 除了 JBind 以外的所有测试都是在不进行完全验证的情况下运行的。大多数数据绑定框架仅按照其设计包含某个固有的验证级别(例如,确保元素的内容模型是匹配的)。大多数框架还可以使用验证解析器(如 Xerces Java 2)在输入时对文档进行完全检查,并且有框架(包括 JAXB)可以在内存中执行绑定数据的完全验证。因为在这些测试中主要关心的是性能,所以我尽可能地禁用了可选验证(包括在 Castor 中使用属性文件和数据分组程序/编组程序设置)。 输出计时
在该领域中,dom4j 提供的性能是所有数据绑定方法中最好的,比 JiBX 稍好一点,比 Zeus 更加好一点。其它数据绑定框架都花费了约两倍的时间,Quick 是所有框架中最慢的(当然,不是故意在说双关语)。尽管这里的结果与输入测试的几乎没有太大的变化,但是 dom4j 的确优于其它任何数据绑定框架的这一事实表明它们仍然有改进的余地。 内存使用情况
这里的差异比时间性能比较中的差异更大,并且表现出了一个非常不同的模式。尽管 dom4j 在时间测量中执行的很好,但是在内存使用方面,它比任何数据绑定框架(除了 JBind,它构建在与 dom4j 的表示相当的内部文档模型上)差远了。与该领域中最优秀的执行者相比,表示相同的数据,dom4j 所占用的内存是前者的 10 倍。 两种映射绑定方法为绑定数据使用了同一种内部结构,所以它们表现出了相同的内存使用情况。这让它们在内存效率的“竞技场”上并列第一,从而产生了比使用生成代码的数据绑定方法优越几倍的性能。部分原因是因为 映射绑定使用了数据值的紧凑表示。在这些测试中,映射绑定将大多数数据值转换成 映射绑定方法之所以能获得较高的内存效率,除了因为它更为广泛地使用了原语值外,另一个原因是生成代码方法通常会将控制信息添加到出现在每个绑定对象中的实际数据中去。该控制信息增加了对象的大小,因而数据绑定少了一个主要优点。 在这些测试中,使用生成代码的数据绑定框架消耗的内存至少是映射绑定的几倍,但是(除 JBind 外)仍然比 dom4j 的文档模型表示小很多。这一点不足为奇 — 诸如 dom4j 的文档模型需要构造一些对象以表示文档的每个组件(包括实际的数据文本以及诸如元素和属性之类的结构组件),而数据绑定只需要保存实际的数据。对于生成代码绑定而言,许多实际数据仍然是作为 这里,Zeus 被认为是唯一直接将所有数据存储为 启动时间
图 7 显示了启动一个短文档所花费的时间 — 从基准测试程序开始执行到整个操作返回为止(将数据分解成对象,然后将对象编组回文档)。同前面的计时数字不同:这里大多数的时间花费在了类装入,以及为获得数据绑定框架代码而由 JVM 进行的本机代码生成。通过将这些结果与前面的计时图表进行比较,可以看到这一启动时间通常比实际处理时间(即使是处理相当大的文档)要大好几倍。如果您的程序每次执行时将只使用一些文档,那么该启动时间将是比前面显示的最佳情形时间更重要的因素。 数据绑定框架使用的 jar 文件的大小是影响这一启动时间的一个主要因素。JiBX 是最小的,运行时和解析器的总大小不足 60KB。JAXB、Castor 和 JBind 是最大的,每个大小大约为 1MB。该时间还受每个框架所需的初始化影响。在使用映射绑定的 Castor 情形中,该时间包含处理映射定义文件,而对于 JBind 而言,它包含处理文档的 Schema 定义。 那么什么是 JiBX 呢?
JiBX 实际上源于本系列文章。当我开始研究可用的数据绑定框架时,我惊奇地看到:与文档模型(如 dom4j)相比,它们并不是执行得都那样好。这与我的期望相反,因为数据绑定方法实际上减少了保存在内存中的文档信息的数量 — 而文档模型在内存中保存所有事物,同时数据绑定只需要实际数据。我认为,数据用得少的方法通常应该比那些数据用得多的方法要快。 在研究现有数据绑定框架是如何操作的过程中,我发现从性能角度来说有两个方面看上去不是很好。第一个方面就是许多框架中广泛使用了反射。反射是在运行时访问有关 Java 语言类的信息的一种方法。可用它来访问类实例中的字段和方法,从而提供了一种在运行时将类动态地挂钩在一起,却无需类之间有任何源代码链接的方法。反射是一种功能非常强大的 Java 技术特性,但是将其与调用方法或直接访问已编译代码中的字段相比,它在性能上有些欠缺。 我质疑的第二个方面是使用 SAX2 解析器对文档进行数据分解。SAX2 是一种非常有用的解析 XML 的标准,但是其事件驱动方法并不非常适合数据绑定和类似的应用程序。这里的问题在于,处理 SAX2 事件的代码需要维护其处理的所有事情的状态信息,这既增加了复杂性又增加了开销。 我创建了形成 JiBX 的代码,以对一些方法(解决其它数据绑定框架中所存在的这些问题)进行测试,并实验扩展超出 Castor 支持范围的映射绑定方法。JiBX 使用字节代码增强而不是反射来在项目构建时将挂钩添加进应用程序代码。JiBX 基于拉(pull)解析器体系结构(当前是 XMLPull),而不是 SAX2。JiBX 不是根据 DTD 或 Schema 生成代码,而是使用绑定定义,该定义将用户提供的类与 XML 结构相关联。 这些技术并不是 JiBX 所特有的。许多 Java 数据对象(Java Data Object,JDO)实现都使用字节代码增强,基本上都是为了达到与 JiBX 相同的目的(将访问挂钩添加到现有的已编译代码中)。原始的 JAXB 代码(已被丢弃)基于类似 XMLPull 的拉解析器体系结构。Castor 和 Quick 都支持数据绑定的映射方法(尽管有一些限制)。即使个别技术不是很新,但是它们的组合仍然可以形成其它数据绑定框架非常有趣的备用方案。 在本系列文章的第 3 部分,我将完整地介绍有关 JiBX 的知识。JiBX 仍然处于初期开发阶段。为了性能测试,我手工编写了代码,通常通过字节代码增强添加该代码,并使用 JiBX 运行时的当时版本来运行它。到本文发表时,我仍在着手完成增强代码,有许多其它特性我希望添加到其中。如果您在第 3 部分发表前就希望了解更多有关 JiBX 的知识,请查阅参考资料以获取到 JiBX 站点的链接。您甚至可以为 JiBX 的未来开发献策献力,也可以在您自己的应用程序中使用 JiBX。 结束语
JAXB 看上去将来仍是代码生成方法的一个不错选择(测试版许可证只允许评估使用)。当前的参考实现测试版在 jar 大小方面非常庞大,并且在内存使用方面的效率也略嫌不足,但是这里再重申一次,将来您可能会看到更佳的性能。在撰写本文的时候,当前版本仍然是测试版,只有当它作为商业或开放源码项目发布以后,它的性能才可能优于参考实现。由于它将作为 J2EE 平台的标准部分,所以关于在 Java 中使用 XML 方面,JAXB 无疑会扮演重要的角色。 性能结果也证实:JBind、Quick 和 Zeus 最适用于有特殊需求的应用程序,而不应该用于一般用途。JBind 的 XML 代码方法可以为围绕 XML 文档处理而构建的应用程序提供重要基础,但是当前实现的性能容易导致问题。Quick 和 Zeus 提供根据 DTD 进行代码生成,但是正如我在第 1 部分中提到的那样,将 DTD 转换成 Schema 通常相当简单。缺点是,Quick 使用起来似乎过于复杂,而 Zeus 只支持 对于数据绑定的映射方法,Castor 的优点是:它是一个相当稳定的实现,并可投入实际使用。Quick 也可以用于这类的绑定,但是似乎也难于设置。JiBX 是新事物,并且尚未完全投入使用,但是它提供了卓越的性能和高度的灵活性。 如果您还未阅读第 1 部分,您也许应该回头阅读一下那篇文章,以便了解更多有关这些数据绑定框架特性的知识。第 1 部分还讨论了数据绑定的代码生成和映射方法之间的权衡。在第 3 部分中,我将深入介绍新的 JiBX 框架。这包括 JiBX 如何将 Java 对象映射到 XML,以及 JiBX 为最小化运行时开销而在构建时使用的字节代码增强过程。请回来查看有关这个令人振奋的方法的完整信息,以提升框架性能!
|