2

「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;
    }
}
4

4 に答える 4

2

完全な答えは次のとおりです。

  1. ObjectOutputStream には、いくつかのタイプのオブジェクトが直列化されるための内部静的キャッシュがいくつかあります ( ObjectStreamClassを参照)。そのため、同じタイプのオブジェクトの後続の直列化は、最初のものより高速です。

  2. ObjectOutputStream.writeObject(別の回答で言及されているように、ユーザー定義のメソッドではなく)のコンパイルを考慮すると、JITコンパイルはパフォーマンスに影響を与える可能性があります。回答で JIT コンパイルについて言及してくれたすべての人に感謝します。

これらは、オブジェクトをシリアル化する代わりにバイト配列を書き込むときに違いがない理由も説明しています。a) 静的キャッシュがない、b)FileOutputStream.write(byte [])ネイティブwriteBytesを呼び出し、JIT コンパイルがほとんど行われない。

于 2013-02-15T17:07:18.310 に答える
1

Javaでは、JIT(Just in Timeコンパイラ)は、メソッドが頻繁に呼び出されたときにコンパイルされます(10.000回呼び出すことをお勧めします)。

しかし、Javaシリアル化は低速であることがわかっており、大量のメモリを使用します。
DataOutputStreamを使用して自分自身をシリアル化すると、より良い結果が得られます。

高速デモプロジェクトの場合、Javaはシリアル化に組み込まれており、箱から出してすぐにバグなしで動作します。

于 2013-02-15T16:42:14.670 に答える
1

JVMはcall count、プログラム内のメソッドごとにを維持します。プログラム内で同じメソッドを呼び出すたびに、そのメソッドはcall count増加します。call countに達するとすぐにJIT compilation threshold、このメソッドはcompiledによって行われJITます。そして、次にこのメソッドが呼び出されると、メソッドインタープリターがネイティブコードを実行するのではなく、メソッドインタープリターが実行されるため、実行が高速になります。したがって、同じメソッドの最初の呼び出しは、後続の呼び出しよりも時間がかかります。

于 2013-02-15T16:43:17.833 に答える
0

初めて実行するとき、JIT コンパイル、クラスの読み込み、リフレクションなど、多くのコストが発生します。これは正常な動作であり、本番アプリへの影響は無視できるため、ほとんどの場合、心配する必要はありません。

于 2013-02-15T16:39:34.923 に答える