【问题标题】:Java Object Serialization Performance tipsJava 对象序列化性能提示
【发布时间】:2009-03-02 12:04:34
【问题描述】:

我必须将一个巨大的对象树 (7,000) 序列化到磁盘中。最初我们使用 Kodo 将这棵树保存在一个数据库中,但是将这棵树加载到内存中会产生成千上万的查询,并且会占用本地宇宙的很大一部分可用时间。

我为此尝试了序列化,并且确实获得了性能提升。但是,我觉得我可以通过编写自己的自定义序列化代码来改进这一点。我需要尽快加载这个序列化对象。

在我的机器中,序列化/反序列化这些对象大约需要 15 秒。从数据库加载它们时,大约需要 40 秒。

考虑到由于对象在树中,它们相互引用,我可以做些什么来提高这种性能?

【问题讨论】:

    标签: java serialization


    【解决方案1】:

    不要忘记对不必序列化的实例变量使用“瞬态”关键字。这可以提高性能,因为您不再读取/写入不必要的数据。

    【讨论】:

    • 无论如何,这是一个很好的、普遍的重要考虑因素。我已经这样做了,但重要的是要提一下。 +1
    【解决方案2】:

    一种优化是自定义类描述符,以便您将类描述符存储在不同的数据库中,并且在对象流中您只能通过 ID 引用它们。这减少了序列化数据所需的空间。例如,请参阅 SerialUtilClassesTable 类如何在一个项目中做到这一点。

    使类 Externalizable 而不是 Serializable 可以带来一些性能优势。缺点是需要大量的手工工作。

    还有其他的序列化库,比如jserial,它可以提供比Java默认序列化更好的性能。此外,如果对象图不包含循环,那么它可以更快地序列化,因为序列化程序不需要跟踪它所看到的对象(请参阅jserial's FAQ 中的“它是如何工作的?”)。

    【讨论】:

    • 我过去做过 Externalizable 路线,在大对象图的序列化/反序列化方面获得了大约 20-23% 的性能提升。为此所需的工作量将与您必须自定义的对象数量成正比。
    【解决方案3】:

    我建议您实现自定义 writeObject()readObject() 方法。 通过这种方式,您将能够为树中的每个节点消除写入子节点。当您使用默认序列化时,每个节点都将与它的所有子节点一起被序列化。

    例如,Tree 类的 writeObject() 应该遍历树的所有节点,并且只写入带有一些标记的节点数据(不包括节点本身),标识树级别。

    您可以查看LinkedList,了解这些方法是如何在那里实现的。它使用相同的方法来防止为每个条目写入上一个和下一个条目。

    【讨论】:

      【解决方案4】:

      为避免编写自己的序列化代码,请尝试Google Protocol Buffers。根据他们的网站:

      协议缓冲区是 Google 用于序列化结构化数据的语言中立、平台中立、可扩展的机制——想想 XML,但更小、更快、更简单。您只需定义一次数据的结构化方式,然后就可以使用特殊生成的源代码轻松地在各种数据流中写入和读取结构化数据,并使用各种语言 - Java、C++ 或 Python

      我没有使用过它,但听到了很多关于它的正面评价。另外,我必须维护一些自定义序列化代码,这绝对是一场噩梦(更不用说追踪错误了),所以让别人为你做这件事总是一件好事。

      【讨论】:

        【解决方案5】:

        这就是我的做法,形成我的头顶

        序列化

        1. 单独序列化每个对象
        2. 为每个对象分配一个唯一的键
        3. 当一个对象持有对另一个对象的引用时,将该对象的唯一键放入序列化中的对象位置。 (我会使用转换为二进制的 UUID)
        4. 使用唯一键将每个对象保存到文件/数据库/存储中

        反序列化

        1. 从任意对象开始(通常是我怀疑的根)对其进行反序列化并将其放入映射中,并将其唯一键作为索引并返回
        2. 当您在序列化流中踩到对象键时,首先检查它是否已经被反序列化,方法是在映射中查找它的唯一键,如果它只是从那里抓取它,如果没有放一个延迟加载代理(为该对象重复这两个步骤),而不是具有钩子的真实对象,可以在您需要时加载正确的对象。

        编辑,如果你有循环引用,你可能需要使用两次序列化和反序列化,这会使事情变得有点复杂 - 但不会那么复杂。

        【讨论】:

        • 这可以工作,但需要修改我拥有的相当多的代码
        • 对标准序列化有什么改进?据我所知,默认机制已经完成了。
        • @saua,因为您可以在需要时延迟加载和实例化每个对象,而不是一次加载所有对象,您也可以自己深入字节级别并优化序列化格式。
        【解决方案6】:

        您是否尝试过压缩流(GZIPOutputStream)?

        【讨论】:

        • 我需要提高加载和存储的性能,但我没有在问题中指定,实际上空间也是一种“性能”衡量标准。
        • 更少的空间意味着更少的磁盘访问意味着更少的时间
        • 仅当序列化过程是磁盘绑定的。它似乎不在我的系统上;它似乎受 CPU 限制,因此压缩只会进一步减慢速度。
        【解决方案7】:

        为了性能,我建议根本不使用 java.io 序列化。而是自己着手处理字节。

        如果你要使用 java.io 序列化树,你可能需要确保你的递归不会变得太深,要么通过展平(如 TreeSet 所做的那样)或安排首先序列化最深的节点(所以您有反向引用而不是嵌套的 readObject 调用)。

        如果在 Kodo 中没有办法一次(或几次)读取整棵树,我会感到惊讶。

        【讨论】:

        • 在 Kodo 中有一种方法可以做到这一点,但问题是它取决于如何在数据库中创建对象。不幸的是,数据库的方式是我们做不到的(也没有办法改变模型)
        【解决方案8】:

        另外,看看XStream,一个将对象序列化为 XML 并再次返回的库。

        【讨论】:

        • 我已经试过了,对于这些类型的物体,它甚至比Kodo还要糟糕。到目前为止,Java 序列化比 XStream 快。
        【解决方案9】:

        您可以使用Colfer 生成bean,Java 的标准序列化性能将获得10 - 1000 倍的提升。除非大小达到 GB 以上,否则您将远远低于一秒。

        【讨论】:

          猜你喜欢
          • 2023-04-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-07-30
          • 1970-01-01
          相关资源
          最近更新 更多