关于xml:Java对象分配开销

关于xml:Java对象分配开销

Java object allocation overhead

我正在用Java编写不可变的DOM树,以简化从多个线程的访问。*

但是,它确实需要尽快支持插入和更新。并且由于它是不可变的,因此,如果我更改树的第N个级别上的节点,则需要至少分配N个新节点才能返回新树。

我的问题是,每次修改树时,预分配节点比创建新节点快得多吗?这将非常容易-保留一个由数百个未使用节点组成的池,并从池中拉出一个而不是在修改操作需要时创建一个。当没有其他事情发生时,我可以补充节点池。 (以防万一,在此应用程序中,执行时间将比堆空间多得多)。

这样做值得吗?关于加快速度的其他提示吗?

或者,有人知道不可变的DOM库吗?我搜索了,但是找不到任何东西。

*注意:对于不熟悉不变性概念的人们,这基本上意味着在对更改它的对象执行任何操作时,该方法会返回该对象的副本,其中包含已更改的内容,而不是更改的对象。因此,如果另一个线程仍在读取该对象,它将继续在"旧"版本上愉快地操作,而不知道已进行了更改,而不是可怕地崩溃。参见http://www.javapractices.com/topic/TopicAction.do?Id=29


这些天来,对象的创建非常快,对象池的概念已经过时了(至少一般而言;连接池当然仍然有效)。

避免过早优化。在制作副本时需要它们时创建节点,然后查看它是否变得异常缓慢。如果是这样,那么请研究一些加快它的技术。但是,除非您已经知道所获得的速度不够快,否则我不会介绍引入池化所需的所有复杂性。


我不想给出一个非答案,但是我认为回答这样一个性能问题的唯一明确方法可能是让您对两种方法进行编码,对两种方法进行基准测试并比较结果。


I'm not sure if you can avoid explicitly synchronizing certain methods in order to make sure everything is thread-safe.

在一种特定情况下,您需要同步一侧或另一侧,以使新创建的节点可用于其他线程,否则,您可能会冒着VM / CPU对字段的写入进行重新排序的麻烦,而对字段的写入要超出对共享的引用的写入节点,公开一个当事人构造的对象。

Try to think in a higher level. You have an IMMUTABLE tree (that is basically a set of nodes pointing to its children). You want to insert a node in it. Then, there's no way out: you have to create a new WHOLE tree.

如果选择将树实现为指向子代的一组节点,则必须沿着更改后的节点到根的路径创建新节点。其他具有与以前相同的值,通常可以共享。因此,您需要创建一个局部的新树,这通常意味着(编辑节点的深度)父节点。

如果您可以处理不太直接的实现,则应该仅使用部分功能来创建节点,而使用类似于"纯功能数据结构"中所述的技术来减少平均创建成本,或者可以使用半功能方法绕过它(例如创建一个包装现有迭代器的迭代器,但是返回新节点而不是旧节点,以及随着时间的流逝修复结构中此类补丁的机制)。在这种情况下,XPath样式的api可能比DOM api更好-它可能使节点与树的耦合解耦更多,并更智能地处理变异的树。


我认为@Outlaw有道理。 DOM树的结构位于节点本身中,节点指向其子节点。要修改树的结构,您必须修改节点,这样就无法将其池化,您必须创建一个新的节点。

尝试更高层次的思考。您有一棵IMMUTABLE树(基本上是指向其子节点的一组节点)。您要在其中插入一个节点。然后,就没有出路了:您必须创建一个新的WHOLE树。

是的,不可变树是线程安全的,但会影响性能。对象创建可能很快,但没有对象创建快。 :)


@Outlaw程序员

When you pull an object out of the
pool, won't you have to invoke a
setter to link up the children?

每个节点不必在包内部是不变的,而只需在向外接口上是不变的。 node.addChild()是具有公开可见性的不可变函数,并返回文档,而node.addChildInternal()将是具有包可见性的常规可变函数。但是由于它在包的内部,因此只能称为addChild()的后代,并且可以保证整个结构是线程安全的(前提是我同步对对象池的访问)。您看到这方面的缺陷了吗?如果是这样,请告诉我!

I think that using immutable nodes is probably not going to give you the kind of thread-safety you need in the first place. What happens if 1 thread is iterating over the nodes (a search or something), while another thread is adding/removing nodes?

整个树是不可变的。假设我有Thread1和Thread2,以及树dom1。线程1在dom1上启动读取操作,而线程2同时在dom1上启动写入操作。但是,Thread2所做的所有更改实际上将对一个新对象dom2进行,而dom1将是不可变的。的确,Thread1读取的值将过期(几微秒),但不会因IndexOutOfBounds或NullPointer异常而崩溃,也不会因读取正在写入的可变对象而崩溃。然后,Thread2可以向Thread1触发一个包含dom2的事件,以便它可以再次读取并更新其结果(如有必要)。

编辑:已澄清


对于您一开始要做的事情,我有些困惑。您希望所有节点都是不可变的,并希望将它们合并?这两个想法不是互斥的吗?将对象从池中拉出时,是否不必调用setter来链接子级?

我认为使用不可变节点可能不会一开始就为您提供所需的线程安全性。如果有一个线程在节点上进行迭代(搜索或其他操作),而另一个线程正在添加/删除节点,会发生什么情况?搜索结果不会无效吗?我不确定是否可以避免显式同步某些方法,以确保所有方法都是线程安全的。


推荐阅读