【问题标题】:First attempt of serialization is slow in Java?Java中序列化的第一次尝试很慢?
【发布时间】:2013-01-31 16:28:31
【问题描述】:

考虑一个使用“ObjectOutputStream”序列化给定数量对象的简单程序(在下面发布)。它多次调用相同的函数将对象序列化为文件。第一次调用比后续调用花费更长的时间(差异取决于被序列化的对象数量):

Serializing 10000 objects...
Time elapsed: 498ms
Time elapsed: 168ms
Time elapsed: 186ms

Serializing 100000 objects...
Time elapsed: 1815ms
Time elapsed: 1352ms
Time elapsed: 1338ms

Serializing 500000 objects...
Time elapsed: 8341ms
Time elapsed: 7247ms
Time elapsed: 7051ms

造成这种差异的原因是什么?我尝试在不序列化的情况下做同样的事情,即写一个字节数组,并没有这样的区别。

更新:如果程序不多次调用同一个方法而是在for循环中序列化对象然后调用该方法,也会发生同样的事情:后续方法调用更快:

"manual" serialization, time elapsed: 535
Time elapsed: 170ms
Time elapsed: 193ms
Time elapsed: 139ms

所以 JIT 编译不会造成这种差异。

代码:

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class SerializationTest {
    static final int COUNT = 10000, TRIES = 3;

    static class Simple implements Serializable {

        String name;
        int index;

        Simple(String name, int index) {
            this.name = name;
            this.index = index;
        }
    }

    public static void main(String[] args) throws IOException {
        int count = COUNT;
        if (args.length > 0) {
            count = Integer.parseInt(args[0]);
        }
        List<Simple> objects = new ArrayList<Simple>();
        for (int i = 0; i < count; i++) {
            objects.add(new Simple("simple" + i, i));
        }
        String filename = args.length > 1 ? args[1] : "objects";

        System.err.println("Serializing " + count + " objects...");
        for(int i = 0; i < TRIES; i++) {
            System.err.println("Time elapsed: " + 
                               serializeOneByOne(objects, filename + i + ".bin", false) + "ms");
        }
    }

    static long serializeOneByOne(List<?> objects, String filename, boolean buffered)
                                                                    throws IOException {
        OutputStream underlying = new FileOutputStream(filename);
        if (buffered) {
            underlying = new BufferedOutputStream(underlying);
        }
        ObjectOutputStream output = new ObjectOutputStream(underlying);
        // take started after the output stream is open
        // although it does not make a big difference
        long started = System.currentTimeMillis();

        try {
            for (Object s : objects) {
                output.writeObject(s);
            }
        } finally {
            output.close();
        }
        long ended = System.currentTimeMillis();
        return ended - started;
    }
}

【问题讨论】:

  • 我猜 JIT 在第一次运行 serializeOneByOne 后会优化你的代码。
  • 是的。这绝对是您对所有 Java 程序所期望的 100%,它们开始缓慢并变得更快

标签: java serialization io


【解决方案1】:

完整的答案是:

  1. ObjectOutputStream 有一些内部静态缓存,用于正在序列化的几种类型的对象(请参阅ObjectStreamClass),因此相同类型对象的后续序列化比第一个更快。

  2. 如果考虑 ObjectOutputStream.writeObject 的编译(而不是另一个答案中提到的用户定义的方法),JIT 编译可能会影响性能。感谢所有在回答中提到 JIT 编译的人。

这些也解释了为什么在写入字节数组而不是序列化对象时没有区别:a) 没有静态缓存和 b) FileOutputStream.write(byte []) 调用本机 writeBytes 并且几乎没有 JIT 编译发生。

【讨论】:

    【解决方案2】:

    在 Java 中,JIT(即时编译器)在它经常调用的方法(有些人建议调用它 10.000 次)时进行编译。

    但是众所周知,java 序列化很慢并且会占用大量内存。
    使用 DataOutputStream 对自己进行序列化时会做得更好。

    如果用于快速演示项目,Java 内置序列化,开箱即用,没有错误。

    【讨论】:

      【解决方案3】:

      JVM 为程序中的每个方法维护一个call count。每次在程序中调用相同的方法时,它的call count 都会增加。只要它的call count 到达JIT compilation threshold,这个方法就是compiled by JIT。并且下次调用这个方法时,它的执行速度会更快,因为现在解释器正在执行本机代码,而不是解释方法。因此,相同方法的第一次调用比后续调用花费更多时间。

      【讨论】:

      • 感谢您的解释。虽然你说的都是真的,但这不是原因,请参阅我的更新。
      【解决方案4】:

      您在第一次运行它时会产生很多成本,JIT 编译、类加载、反射等。这是正常的,而且大多数时候无需担心,因为对生产应用程序的影响是微不足道。

      【讨论】:

        猜你喜欢
        • 2018-02-02
        • 1970-01-01
        • 2012-01-27
        • 2017-05-23
        • 2021-11-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多