Java 对象分配开销

Java object allocation overhead

提问人:levand 提问时间:9/4/2008 最后编辑:Babylevand 更新时间:5/23/2014 访问量:1506

问:

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

但是,它确实需要尽可能快地支持插入和更新。由于它是不可变的,如果我对树的第 N 级节点进行更改,我需要分配至少 N 个新节点才能返回新树。

我的问题是,预先分配节点而不是每次修改树时创建新节点会不会快得多?这很容易做到 - 保留一个由数百个未使用的节点组成的池,然后从池中拉出一个节点,而不是在修改操作需要时创建一个。当没有其他事情发生时,我可以补充节点池。(如果不是很明显,在这个应用程序中,执行时间将比堆空间更宝贵)

这样做值得吗?还有其他关于加快速度的技巧吗?

或者,有谁知道是否已经有一个不可变的 DOM 库?我搜索了一下,但什么也没找到。

*注意:对于那些不熟悉不可变性概念的人来说,它基本上意味着在对对象进行任何更改它的操作时,该方法返回包含更改的对象的副本,而不是更改后的对象。因此,如果另一个线程仍在读取该对象,它将继续在“旧”版本上愉快地运行,而不知道已经进行了更改,而不是可怕地崩溃。查看 http://www.javapractices.com/topic/TopicAction.do?Id=29

Java XML DOM 并发

评论


答:

3赞 matt b 9/4/2008 #1

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

12赞 jodonnell 9/4/2008 #2

如今,对象创建速度非常快,对象池的概念已经过时了(至少在一般情况下是这样;连接池当然仍然有效)。

避免过早优化。在执行副本时需要节点时创建节点,然后查看速度是否变得非常慢。如果是这样,那么请研究一些技术来加快速度。但是,除非你已经知道你所拥有的还不够快,否则我不会去介绍你进行池化所需的所有复杂性。

评论

0赞 mikera 6/17/2010
+1 表示正确答案。如今,在池中查找对象(尤其是以线程安全的方式)通常比创建对象慢。同样值得注意的是,各种库(以及 Clojure)有效地为其所有不可变数据结构做到这一点。所以这是一个久经考验的方法。
0赞 Tim Frey 9/4/2008 #3

我有点困惑你首先想做什么。您希望所有节点都是不可变的,并且想要将它们池化?这两个想法不是相互排斥的吗?当你从池中拉出一个对象时,你是否不必调用一个 setter 来链接子对象?

我认为使用不可变节点可能不会给你带来你首先需要的那种线程安全。如果一个线程正在迭代节点(搜索或其他东西),而另一个线程正在添加/删除节点,会发生什么?搜索结果不会无效吗?我不确定您是否可以避免显式同步某些方法,以确保一切都是线程安全的。

0赞 levand 9/4/2008 #4

@Outlaw程序员

当您将对象从 池,你不必调用一个 二传手把孩子们联系起来?

每个节点不需要在包内部不可变,只需要对外向接口不可变。 将是一个具有公共可见性的不可变函数并返回一个文档,而 wheras将是一个具有包可见性的普通可变函数。但是由于它是包内部的,因此它只能作为 的后代调用,并且整个结构保证是线程安全的(前提是我同步访问对象池)。你看到这其中的缺陷了吗......?如果是这样,请告诉我!node.addChild()node.addChildInternal()addChild()

我认为使用不可变节点可能不会给你带来你首先需要的那种线程安全。如果一个线程正在迭代节点(搜索或其他东西),而另一个线程正在添加/删除节点,会发生什么?

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

编辑:澄清

0赞 Marcio Aguiar 9/4/2008 #5

我认为@Outlaw说得有道理。DOM 树的结构驻留在节点本身中,有一个节点指向其子节点。要修改树的结构,你必须修改节点,所以你不能让它池化,你必须创建一个新的节点。

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

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

评论

1赞 mikera 6/17/2010
你需要创建一个全新的树是不正确的 - 事实上,你只需要创建一个新节点的祖先的副本。请参阅“持久性数据结构”。
1赞 Pete Kirkham 9/4/2008 #6

我不确定您是否可以避免显式同步某些方法,以确保一切都是线程安全的。

在一种特定情况下,您需要同步使新创建的节点可供其他线程使用的一侧或另一侧,否则,VM/CPU 可能会对字段的写入重新排序,从而在对共享节点的引用写入之后重新排序,从而公开参与方构造的对象。

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

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

如果你能应对一个不太直接的实现,你应该能够只创建节点的一部分,使用类似于纯函数式数据结构中描述的技术来降低创建的平均成本,或者你可以使用半函数式方法绕过它(例如创建一个迭代器来包装一个现有的迭代器, 但返回新节点而不是旧节点,以及随着时间的推移修复结构中此类补丁的机制)。在这种情况下,XPath 风格的 api 可能比 DOM api 更好 - 它可能会使节点与树分离得更多一些,并更智能地处理突变的树。