首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

加紧Java的文件序列化速度

2012-12-20 
加快Java的文件序列化速度自从第一个Java版本开始,很多开发人员一直都在尝试让Java获得最少和C/C++一样的

加快Java的文件序列化速度

自从第一个Java版本开始,很多开发人员一直都在尝试让Java获得最少和C/C++一样的表现。JVM提供商尽他们最大的努力去实现一些新的JIT算法,但是还是有很多需要做的,特别是在我们使用Java的方法上。

例如,在对象<->文件序列化上就差距很大--尤其在读写内存对象上。我将就这个主题做一些解释和分享。

所有的测试都是在下面这个对象上执行的:

1public class TestObject implements Serializable {2?3??private long longVariable;4??private long[] longArray;5??private String stringObject;6??private String secondStringObject; //just for testing nulls7?8??/* getters and setters */9}

?

为了简单起见,我将只贴出写入方法(尽管读取类似),完整的源码在我的GitHub上可以找到(http://github.com/jkubrynski/serialization-tests)

最标准的java序列化(我们都是从这里学起的)是这样的:

01public void testWriteBuffered(TestObject test, String fileName) throws IOException {02??ObjectOutputStream objectOutputStream = null;03??try {04????FileOutputStream fos = new FileOutputStream(fileName);05????BufferedOutputStream bos = new BufferedOutputStream(fos);06????objectOutputStream = new ObjectOutputStream(bos);07????objectOutputStream.writeObject(test);08??} finally {09????if (objectOutputStream != null) {10??????objectOutputStream.close();11????}12??}13}

?

提升标准序列化速度的最简单方法时使用RandomAccessFile对象:

01public void testWriteBuffered(TestObject test, String fileName) throws IOException {02??ObjectOutputStream objectOutputStream = null;03??try {04????RandomAccessFile raf = new RandomAccessFile(fileName, "rw");05????FileOutputStream fos = new FileOutputStream(raf.getFD());06????objectOutputStream = new ObjectOutputStream(fos);07????objectOutputStream.writeObject(test);08??} finally {09????if (objectOutputStream != null) {10??????objectOutputStream.close();11????}????? 12}

?

更高深点的技术是使用Kryo框架,新旧版本的差距是很大的,我做过测试。因为性能比较上并没有体现出特别引人注意的差异,所以我将使用2.x版本,因为它对用户更友好而且更快些。

01private static Kryo kryo = new Kryo(); // version 2.x02?03public void testWriteBuffered(TestObject test, String fileName) throws IOException {04??Output output = null;05??try {06????RandomAccessFile raf = new RandomAccessFile(fileName, "rw");07????output = new Output(new FileOutputStream(raf.getFD()), MAX_BUFFER_SIZE);08????kryo.writeObject(output, test);09??} finally {10????if (output != null) {11??????output.close();12????}13??}14}

?

最后一个方案是在Martin Thompson的文章中提到的(Native C/C++ Like Performance For Java Object Serialisation),介绍了怎样在Java中像C++那样和内存打交道。

01public void testWriteBuffered(TestObject test, String fileName) throws IOException {02??RandomAccessFile raf = null;03??try {04????MemoryBuffer memoryBuffer = new MemoryBuffer(MAX_BUFFER_SIZE);05????raf = new RandomAccessFile(fileName, "rw");06????test.write(memoryBuffer);07????raf.write(memoryBuffer.getBuffer());08??} catch (IOException e) {09????if (raf != null) {10??????raf.close();11????}12??}13}

?

TestObject写入方法如下:

01public void write(MemoryBuffer unsafeBuffer) {02??unsafeBuffer.putLong(longVariable);03??unsafeBuffer.putLongArray(longArray);04??// we support nulls05??boolean objectExists = stringObject != null;06??unsafeBuffer.putBoolean(objectExists);07??if (objectExists) {08????unsafeBuffer.putCharArray(stringObject.toCharArray());09??}10??objectExists = secondStringObject != null;11??unsafeBuffer.putBoolean(objectExists);12??if (objectExists) {13????unsafeBuffer.putCharArray(secondStringObject.toCharArray());14??}15}

?

直接内存缓冲区类(已简化了的,仅仅为了展示这个思想)

01public class MemoryBuffer {02??// getting Unsafe by reflection03??public static final Unsafe unsafe = UnsafeUtil.getUnsafe();04?05??private final byte[] buffer;06?07??private static final long byteArrayOffset = unsafe.arrayBaseOffset(byte[].class);08??private static final long longArrayOffset = unsafe.arrayBaseOffset(long[].class);09??/* other offsets */10?11??private static final int SIZE_OF_LONG = 8;12??/* other sizes */13?14??private long pos = 0;15?16??public MemoryBuffer(int bufferSize) {17????this.buffer = new byte[bufferSize];18??}19?20??public final byte[] getBuffer() {21????return buffer;22??}23?24??public final void putLong(long value) {25????unsafe.putLong(buffer, byteArrayOffset + pos, value);26????pos += SIZE_OF_LONG;27??}28?29??public final long getLong() {30????long result = unsafe.getLong(buffer, byteArrayOffset + pos);31????pos += SIZE_OF_LONG;32????return result;33??}34?35??public final void putLongArray(final long[] values) {36????putInt(values.length);37????long bytesToCopy = values.length << 3;38????unsafe.copyMemory(values, longArrayOffset, buffer, byteArrayOffset + pos, bytesToCopy);39????pos += bytesToCopy;40??}41?42?43??public final long[] getLongArray() {44????int arraySize = getInt();45????long[] values = new long[arraySize];46????long bytesToCopy = values.length << 3;47????unsafe.copyMemory(buffer, byteArrayOffset + pos, values, longArrayOffset, bytesToCopy);48????pos += bytesToCopy;49????return values;50??}51?52??/* other methods */53}

?

几个小时的Caliper测试结果如下:

?Full trip [ns]?Standard deviation [ns]?Standard??207307?2362Standard on RAF?42661?733KRYO 1.x??12027?112KRYO 2.x?11479?259Unsafe?8554?91

在最后我们可以得出一些结论:

Unsafe序列化比标准的java.io.Serizlizable快了23倍使用RandomAccessFile可以使标准的有缓冲序列化加速将近4倍Kryo-dynamic序列化大约比手写实现的直接缓冲满了35%

最后,就像我们看到的那样,还是没有绝对的答案。对于我们中的大多数人来说,获得3000ns(0.003ms)的速度提升是不值得为每个需要序列化的对象来写单独实现的。在标准的方案中,我们大多数选择Kryo?。然而,在惜时如金的低延时系统中,这个选择将会是完全不同的。

热点排行