您的位置:首页 > 编程语言 > Java开发

各种Java序列化性能比较

2016-06-05 12:21 477 查看
这里比较Java对象序列化 XML JSON Kryo POF等序列化性能比较。

很多人以为JDK的Java序列化肯定是将Java对象转换成二进制序列化最快的方式,JDK7出来以后,我们发现实际上每次新的JDK比旧版本快。

我们通常以为将Java对象序列化成二进制比序列化成XML或Json更快,其实是错误的,如果你关心性能,建议避免Java序列化。

Java序列化有很多的要求,最主要的一个是包含能够序列化任何东西(或至少任何实现Serializable接口)。这样才能进入其他JVM之中,这很重要,所以有时性能不是主要的要求,标准的格式才最重要。

我们经常看到CPU花费很多时间内进行Java序列化,下面我们研究一下,假设一定Order,虽然只有几个字节,但是序列化以后不是几十个字节,而是600多个字节:

Ordr代码:

public class Order implements Serializable {

    private long id;

    private String description;

    private BigDecimal totalCost = BigDecimal.valueOf(0);

    private List orderLines = new ArrayList();

    private Customer customer;

...

}

序列化输出:

----sr--model.Order----h#-----J--idL--customert--Lmodel/Customer;L--descriptiont--Ljava/lang/String;L--orderLinest--Ljava/util/List;L--totalCostt--Ljava/math/BigDecimal;xp--------ppsr--java.util.ArrayListx-----a----I--sizexp----w-----sr--model.OrderLine--&-1-S----I--lineNumberL--costq-~--L--descriptionq-~--L--ordert--Lmodel/Order;xp----sr--java.math.BigDecimalT--W--(O---I--scaleL--intValt--Ljava/math/BigInteger;xr--java.lang.Number-----------xp----sr--java.math.BigInteger-----;-----I--bitCountI--bitLengthI--firstNonzeroByteNumI--lowestSetBitI--signum[--magnitudet--[Bxq-~----------------------ur--[B------T----xp----xxpq-~--xq-~--

正如你可能已经注意到,Java序列化写入不仅是完整的类名,也包含整个类的定义,包含所有被引用的类。类定义可以是相当大的,也许构成了性能和效率的问题,当然这是编写一个单一的对象。如果您正在编写了大量相同的类的对象,这时类定义的开销通常不是一个大问题。另一件事情是,如果你的对象有一类的引用(如元数据对象),那么Java序列化将写入整个类的定义,不只是类的名称,因此,使用Java序列化写出元数据(meta-data)是非常昂贵的。


Externalizable

通过实现Externalizable接口,这是可能优化Java序列化的。实现此接口,避免写出整个类定义,只是类名被写入。它需要你实施readExternal和writeExternal方法方法的,所以需要做一些工作,但相比仅仅是实现Serializable更快,更高效。

Externalizable对小数目对象有效的多。但是对大量对象,或者重复对象,则效率低。

public class Order implements Externalizable {

    private long id;

    private String description;

    private BigDecimal totalCost = BigDecimal.valueOf(0);

    private List orderLines = new ArrayList();

    private Customer customer;

    public Order() {

    }

    public void readExternal(ObjectInput stream) throws IOException, ClassNotFoundException {

        this.id = stream.readLong();

        this.description = (String)stream.readObject();

        this.totalCost = (BigDecimal)stream.readObject();

        this.customer = (Customer)stream.readObject();

        this.orderLines = (List)stream.readObject();

    }

    public void writeExternal(ObjectOutput stream) throws IOException {

        stream.writeLong(this.id);

        stream.writeObject(this.description);

        stream.writeObject(this.totalCost);

        stream.writeObject(this.customer);

        stream.writeObject(this.orderLines);

    }

}

序列化输出:

----sr--model.Order---*3--^---xpw---------psr--java.math.BigDecimalT--W--(O---I--scaleL--intValt--Ljava/math/BigInteger;xr--java.lang.Number-----------xp----sr--java.math.BigInteger-----;-----I--bitCountI--bitLengthI--firstNonzeroByteNumI--lowestSetBitI--signum[--magnitudet--[Bxq-~----------------------ur--[B------T----xp----xxpsr--java.util.ArrayListx-----a----I--sizexp----w-----sr--model.OrderLine-!!|---S---xpw-----pq-~--q-~--xxx


EclipseLink MOXy - XML 和 JSON

序列化成XML或JSON可以允许其他语言访问,可以实现REST服务等。缺点是文本格式的效率比优化的二进制格式低一些,使用JAXB,你需要使用JAXB注释类,或提供一个XML配置文件。使用@XmlIDREF处理循环。

@XmlRootElement

public class Order {

    @XmlID

    @XmlAttribute

    private long id;

    @XmlAttribute

    private String description;

    @XmlAttribute

    private BigDecimal totalCost = BigDecimal.valueOf(0);

    private List orderLines = new ArrayList();

    private Customer customer;

}

public class OrderLine {

    @XmlIDREF

    private Order order;

    @XmlAttribute

    private int lineNumber;

    @XmlAttribute

    private String description;

    @XmlAttribute

    private BigDecimal cost = BigDecimal.valueOf(0);

}

XML输出:

<order id="0" totalCost="0">

<orderLines lineNumber="1" cost="0">

<order>0</order

></orderLines

></order>

JSOn输出:

{"order":{"id":0,"totalCost":0,"orderLines":[{"lineNumber":1,"cost":0,"order":0}]}}


Kryo

Kryo 是一种快速,高效的序列化的Java框架。 KRYO是新的BSD许可下一个开源项目提供。这是一个很小的项目,只有3名成员,它首先在2009年出品。

工作原理类似于Java序列化KRYO,尊重瞬态字段,但不要求一类是可序列化的。KRYO有一定的局限性,比如需要有一个默认的构造函数的类,在序列化将java.sql.Time java.sql.Date java.sql.Timestamp类会遇到一些问题。

order序列化结果:

------java-util-ArrayLis-----model-OrderLin----java-math-BigDecima---------model-Orde-----


Oracle Coherence POF

 Oracle Coherence 产品提供其自己优化的二进制格式,称为POF
(可移植对象格式) 。 Oracle Coherence的是一个内存中的数据网格解决方案(分布式缓存) 。是一个商业产品,并需要许可证。

POF提供了一个序列化框架,并可以独立使用。 POF要求类实现一个PortableObject接口和读/写方法。您还可以实现一个单独的序列化类,或使用最新版本的序列化的注解。 POF要求每个类都被提前分配一个固定ID,所以你需要通过某种方式确定这个ID 。 POF格式是二进制格式,非常紧凑,高效,快速的,但确实需要你付出一些工作。

POF的总字节数为一个单一的订单/订单行对象为32个字节, 1593字节100 OrderLines的。我不会放弃的结果, POF是一个商业许可产品的一部分,但是是非常快的。

public class Order implements PortableObject {

    private long id;

    private String description;

    private BigDecimal totalCost = BigDecimal.valueOf(0);

    private List orderLines = new ArrayList();

    private Customer customer;

    public Order() {

    }

    public void readExternal(PofReader in) throws IOException {

        this.id = in.readLong(0);

        this.description = in.readString(1);

        this.totalCost = in.readBigDecimal(2);

        this.customer = (Customer)in.readObject(3);

        this.orderLines = (List)in.readCollection(4, new ArrayList());

    }

    public void writeExternal(PofWriter out) throws IOException {

        out.writeLong(0, this.id);

        out.writeString(1, this.description);

        out.writeBigDecimal(2, this.totalCost);

        out.writeObject(3, this.customer);

        out.writeCollection(4, this.orderLines);

    }

}

序列化结果:
-----B--G---d-U------A--G-------


 


性能比较

一个订单包含一个Oderline

SerializerSize (bytes)Serialize (operations/second)Deserialize (operations/second)% Difference (from Java serialize)% Difference (deserialize)
Java Serializable636128,63419,1800%0%
Java Externalizable435160,54926,67824%39%
EclipseLink MOXy XML101348,05647,334170%146%
Kryo90359,368346,984179%1709%
一个订单100个oderlines:

SerializerSize (bytes)Serialize (operations/second)Deserialize (operations/second)% Difference (from Java serialize)% Difference (deserialize)
Java Serializable2,71516,47010,2150%0%
Java Externalizable2,81116,20611,483-1%12%
EclipseLink MOXy XML6,6287,3042,731-55%-73%
Kryo121622,86231,49938%208%
本教程代码下载

要获得象C那样的序列化性能,直接自己编写。


Serialization ByteBuffer Unsafe三者性能比较:

三者性能测试代码:

import sun.misc.Unsafe;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;

import java.io.Serializable;

import java.lang.reflect.Field;

import java.nio.ByteBuffer;

import java.util.Arrays;

public final class TestSerialisationPerf

{

    public static final int REPETITIONS = 1 * 1000 * 1000;

    private static ObjectToBeSerialised ITEM =

        new ObjectToBeSerialised(

            1010L, true, 777, 99,

            new double[]{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0},

            new long[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10});

 

    public static void main(final String[] arg) throws Exception

    {

        for (final PerformanceTestCase testCase : testCases)

        {

            for (int i = 0; i < 5; i++)

            {

                testCase.performTest();

                System.out.format('%d %s\twrite=%,dns read=%,dns total=%,dns\n',

                                  i,

                                  testCase.getName(),

                                  testCase.getWriteTimeNanos(),

                                  testCase.getReadTimeNanos(),

                                  testCase.getWriteTimeNanos() + 

                                  testCase.getReadTimeNanos());

                if (!ITEM.equals(testCase.getTestOutput()))

                {

                    throw new IllegalStateException('Objects do not match');

                }

                System.gc();

                Thread.sleep(3000);

            }

        }

    }

    private static final PerformanceTestCase[] testCases =

    {

        new PerformanceTestCase('Serialisation', REPETITIONS, ITEM)

        {

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            public void testWrite(ObjectToBeSerialised item) throws Exception

            {

                for (int i = 0; i < REPETITIONS; i++)

                {

                    baos.reset();

                    ObjectOutputStream oos = new ObjectOutputStream(baos);

                    oos.writeObject(item);

                    oos.close();

                }

            }

            public ObjectToBeSerialised testRead() throws Exception

            {

                ObjectToBeSerialised object = null;

                for (int i = 0; i < REPETITIONS; i++)

                {

                    ByteArrayInputStream bais = 

                        new ByteArrayInputStream(baos.toByteArray());

                    ObjectInputStream ois = new ObjectInputStream(bais);

                    object = (ObjectToBeSerialised)ois.readObject();

                }

                return object;

            }

        },

        new PerformanceTestCase('ByteBuffer', REPETITIONS, ITEM)

        {

            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

            public void testWrite(ObjectToBeSerialised item) throws Exception

            {

                for (int i = 0; i < REPETITIONS; i++)

                {

                    byteBuffer.clear();

                    item.write(byteBuffer);

                }

            }

            public ObjectToBeSerialised testRead() throws Exception

            {

                ObjectToBeSerialised object = null;

                for (int i = 0; i < REPETITIONS; i++)

                {

                    byteBuffer.flip();

                    object = ObjectToBeSerialised.read(byteBuffer);

                }

                return object;

            }

        },

        new PerformanceTestCase('UnsafeMemory', REPETITIONS, ITEM)

        {

            UnsafeMemory buffer = new UnsafeMemory(new byte[1024]);

            public void testWrite(ObjectToBeSerialised item) throws Exception

            {

                for (int i = 0; i < REPETITIONS; i++)

                {

                    buffer.reset();

                    item.write(buffer);

                }

            }

            public ObjectToBeSerialised testRead() throws Exception

            {

                ObjectToBeSerialised object = null;

                for (int i = 0; i < REPETITIONS; i++)

                {

                    buffer.reset();

                    object = ObjectToBeSerialised.read(buffer);

                }

                return object;

            }

        },

    };

}

abstract class PerformanceTestCase

{

    private final String name;

    private final int repetitions;

    private final ObjectToBeSerialised testInput;

    private ObjectToBeSerialised testOutput;

    private long writeTimeNanos;

    private long readTimeNanos;

    public PerformanceTestCase(final String name, final int repetitions,

                               final ObjectToBeSerialised testInput)

    {

        this.name = name;

        this.repetitions = repetitions;

        this.testInput = testInput;

    }

    public String getName()

    {

        return name;

    }

    public ObjectToBeSerialised getTestOutput()

    {

        return testOutput;

    }

    public long getWriteTimeNanos()

    {

        return writeTimeNanos;

    }

    public long getReadTimeNanos()

    {

        return readTimeNanos;

    }

    public void performTest() throws Exception

    {

        final long startWriteNanos = System.nanoTime();

        testWrite(testInput);

        writeTimeNanos = (System.nanoTime() - startWriteNanos) / repetitions;

        final long startReadNanos = System.nanoTime();

        testOutput = testRead();

        readTimeNanos = (System.nanoTime() - startReadNanos) / repetitions;

    }

    public abstract void testWrite(ObjectToBeSerialised item) throws Exception;

    public abstract ObjectToBeSerialised testRead() throws Exception;

}

class ObjectToBeSerialised implements Serializable

{

    private static final long serialVersionUID = 10275539472837495L;

    private final long sourceId;

    private final boolean special;

    private final int orderCode;

    private final int priority;

    private final double[] prices;

    private final long[] quantities;

    public ObjectToBeSerialised(final long sourceId, final boolean special,

                                final int orderCode, final int priority,

                                final double[] prices, final long[] quantities)

    {

        this.sourceId = sourceId;

        this.special = special;

        this.orderCode = orderCode;

        this.priority = priority;

        this.prices = prices;

        this.quantities = quantities;

    }

    public void write(final ByteBuffer byteBuffer)

    {

        byteBuffer.putLong(sourceId);

        byteBuffer.put((byte)(special ? 1 : 0));

        byteBuffer.putInt(orderCode);

        byteBuffer.putInt(priority);

        byteBuffer.putInt(prices.length);

        for (final double price : prices)

        {

            byteBuffer.putDouble(price);

        }

        byteBuffer.putInt(quantities.length);

        for (final long quantity : quantities)

        {

            byteBuffer.putLong(quantity);

        }

    }

    public static ObjectToBeSerialised read(final ByteBuffer byteBuffer)

    {

        final long sourceId = byteBuffer.getLong();

        final boolean special = 0 != byteBuffer.get();

        final int orderCode = byteBuffer.getInt();

        final int priority = byteBuffer.getInt();

        final int pricesSize = byteBuffer.getInt();

        final double[] prices = new double[pricesSize];

        for (int i = 0; i < pricesSize; i++)

        {

            prices[i] = byteBuffer.getDouble();

        }

        final int quantitiesSize = byteBuffer.getInt();

        final long[] quantities = new long[quantitiesSize];

        for (int i = 0; i < quantitiesSize; i++)

        {

            quantities[i] = byteBuffer.getLong();

        }

        return new ObjectToBeSerialised(sourceId, special, orderCode, 

                                        priority, prices, quantities);

    }

    public void write(final UnsafeMemory buffer)

    {

        buffer.putLong(sourceId);

        buffer.putBoolean(special);

        buffer.putInt(orderCode);

        buffer.putInt(priority);

        buffer.putDoubleArray(prices);

        buffer.putLongArray(quantities);

    }

    public static ObjectToBeSerialised read(final UnsafeMemory buffer)

    {

        final long sourceId = buffer.getLong();

        final boolean special = buffer.getBoolean();

        final int orderCode = buffer.getInt();

        final int priority = buffer.getInt();

        final double[] prices = buffer.getDoubleArray();

        final long[] quantities = buffer.getLongArray();

        return new ObjectToBeSerialised(sourceId, special, orderCode, 

                                        priority, prices, quantities);

    }

    @Override

    public boolean equals(final Object o)

    {

        if (this == o)

        {

            return true;

        }

        if (o == null || getClass() != o.getClass())

        {

            return false;

        }

        final ObjectToBeSerialised that = (ObjectToBeSerialised)o;

        if (orderCode != that.orderCode)

        {

            return false;

        }

        if (priority != that.priority)

        {

            return false;

        }

        if (sourceId != that.sourceId)

        {

            return false;

        }

        if (special != that.special)

        {

            return false;

        }

        if (!Arrays.equals(prices, that.prices))

        {

            return false;

        }

        if (!Arrays.equals(quantities, that.quantities))

        {

            return false;

        }

        return true;

    }

}

class UnsafeMemory

{

    private static final Unsafe unsafe;

    static

    {

        try

        {

            Field field = Unsafe.class.getDeclaredField('theUnsafe');

            field.setAccessible(true);

            unsafe = (Unsafe)field.get(null);

        }

        catch (Exception e)

        {

            throw new RuntimeException(e);

        }

    }

    private static final long byteArrayOffset = unsafe.arrayBaseOffset(byte[].class);

    private static final long longArrayOffset = unsafe.arrayBaseOffset(long[].class);

    private static final long doubleArrayOffset = unsafe.arrayBaseOffset(double[].class);

    private static final int SIZE_OF_BOOLEAN = 1;

    private static final int SIZE_OF_INT = 4;

    private static final int SIZE_OF_LONG = 8;

    private int pos = 0;

    private final byte[] buffer;

    public UnsafeMemory(final byte[] buffer)

    {

        if (null == buffer)

        {

            throw new NullPointerException('buffer cannot be null');

        }

        this.buffer = buffer;

    }

    public void reset()

    {

        this.pos = 0;

    }

    public void putBoolean(final boolean value)

    {

        unsafe.putBoolean(buffer, byteArrayOffset + pos, value);

        pos += SIZE_OF_BOOLEAN;

    }

    public boolean getBoolean()

    {

        boolean value = unsafe.getBoolean(buffer, byteArrayOffset + pos);

        pos += SIZE_OF_BOOLEAN;

        return value;

    }

    public void putInt(final int value)

    {

        unsafe.putInt(buffer, byteArrayOffset + pos, value);

        pos += SIZE_OF_INT;

    }

    public int getInt()

    {

        int value = unsafe.getInt(buffer, byteArrayOffset + pos);

        pos += SIZE_OF_INT;

        return value;

    }

    public void putLong(final long value)

    {

        unsafe.putLong(buffer, byteArrayOffset + pos, value);

        pos += SIZE_OF_LONG;

    }

    public long getLong()

    {

        long value = unsafe.getLong(buffer, byteArrayOffset + pos);

        pos += SIZE_OF_LONG;

        return value;

    }

    public void putLongArray(final long[] values)

    {

        putInt(values.length);

        long bytesToCopy = values.length << 3;

        unsafe.copyMemory(values, longArrayOffset,

                          buffer, byteArrayOffset + pos,

                          bytesToCopy);

        pos += bytesToCopy;

    }

    public long[] getLongArray()

    {

        int arraySize = getInt();

        long[] values = new long[arraySize];

        long bytesToCopy = values.length << 3;

        unsafe.copyMemory(buffer, byteArrayOffset + pos,

                          values, longArrayOffset,

                          bytesToCopy);

        pos += bytesToCopy;

        return values;

    }

    public void putDoubleArray(final double[] values)

    {

        putInt(values.length);

        long bytesToCopy = values.length << 3;

        unsafe.copyMemory(values, doubleArrayOffset,

                          buffer, byteArrayOffset + pos,

                          bytesToCopy);

        pos += bytesToCopy;

    }

    public double[] getDoubleArray()

    {

        int arraySize = getInt();

        double[] values = new double[arraySize];

        long bytesToCopy = values.length << 3;

        unsafe.copyMemory(buffer, byteArrayOffset + pos,

                          values, doubleArrayOffset,

                          bytesToCopy);

        pos += bytesToCopy;

        return values;

    }

}

测试结果:

2.8GHz Nehalem - Java 1.7.0_04

==============================

0 Serialisation write=2,517ns read=11,570ns total=14,087ns

1 Serialisation write=2,198ns read=11,122ns total=13,320ns

2 Serialisation write=2,190ns read=11,011ns total=13,201ns

3 Serialisation write=2,221ns read=10,972ns total=13,193ns

4 Serialisation write=2,187ns read=10,817ns total=13,004ns

0 ByteBuffer write=264ns read=273ns total=537ns

1 ByteBuffer write=248ns read=243ns total=491ns

2 ByteBuffer write=262ns read=243ns total=505ns

3 ByteBuffer write=300ns read=240ns total=540ns

4 ByteBuffer write=247ns read=243ns total=490ns

0 UnsafeMemory write=99ns read=84ns total=183ns

1 UnsafeMemory write=53ns read=82ns total=135ns

2 UnsafeMemory write=63ns read=66ns total=129ns

3 UnsafeMemory write=46ns read=63ns total=109ns

4 UnsafeMemory write=48ns read=58ns total=106ns

2.4GHz Sandy Bridge - Java 1.7.0_04

===================================

0 Serialisation write=1,940ns read=9,006ns total=10,946ns

1 Serialisation write=1,674ns read=8,567ns total=10,241ns

2 Serialisation write=1,666ns read=8,680ns total=10,346ns

3 Serialisation write=1,666ns read=8,623ns total=10,289ns

4 Serialisation write=1,715ns read=8,586ns total=10,301ns

0 ByteBuffer write=199ns read=198ns total=397ns

1 ByteBuffer write=176ns read=178ns total=354ns

2 ByteBuffer write=174ns read=174ns total=348ns

3 ByteBuffer write=172ns read=183ns total=355ns

4 ByteBuffer write=174ns read=180ns total=354ns

0 UnsafeMemory write=38ns read=75ns total=113ns

1 UnsafeMemory write=26ns read=52ns total=78ns

2 UnsafeMemory write=26ns read=51ns total=77ns

3 UnsafeMemory write=25ns read=51ns total=76ns

4 UnsafeMemory write=27ns read=50ns total=77ns

很显然允许自己内存操作的 Unsafe性能是最快的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息