例如,在对象<->文件序列化上就差距很大--尤其在读写内存对象上。我将就这个主题做一些解释和分享。
所有的测试都是在下面这个对象上执行的:
public class TestObject implements Serializable { private long longVariable; private long[] longArray; private String stringObject; private String secondStringObject; //just for testing nulls /* getters and setters */ }
为了简单起见,我将只贴出写入方法(尽管读取类似),完整的源码在我的GitHub上可以找到(http://github.com/jkubrynski/serialization-tests)
最标准的java序列化(我们都是从这里学起的)是这样的:
public void testWriteBuffered(TestObject test, String fileName) throws IOException { ObjectOutputStream objectOutputStream = null; try { FileOutputStream fos = new FileOutputStream(fileName); BufferedOutputStream bos = new BufferedOutputStream(fos); objectOutputStream = new ObjectOutputStream(bos); objectOutputStream.writeObject(test); } finally { if (objectOutputStream != null) { objectOutputStream.close(); } } }
提升标准序列化速度的最简单方法时使用RandomAccessFile对象:
public void testWriteBuffered(TestObject test, String fileName) throws IOException { ObjectOutputStream objectOutputStream = null; try { RandomAccessFile raf = new RandomAccessFile(fileName, "rw"); FileOutputStream fos = new FileOutputStream(raf.getFD()); objectOutputStream = new ObjectOutputStream(fos); objectOutputStream.writeObject(test); } finally { if (objectOutputStream != null) { objectOutputStream.close(); } }
更高深点的技术是使用Kryo框架,新旧版本的差距是很大的,我做过测试。因为性能比较上并没有体现出特别引人注意的差异,所以我将使用2.x版本,因为它对用户更友好而且更快些。
private static Kryo kryo = new Kryo(); // version 2.x public void testWriteBuffered(TestObject test, String fileName) throws IOException { Output output = null; try { RandomAccessFile raf = new RandomAccessFile(fileName, "rw"); output = new Output(new FileOutputStream(raf.getFD()), MAX_BUFFER_SIZE); kryo.writeObject(output, test); } finally { if (output != null) { output.close(); } } }
|
|
最后一个方案是在Martin Thompson的文章中提到的(Native C/C++ Like Performance For Java Object Serialisation),介绍了怎样在Java中像C++那样和内存打交道。
public void testWriteBuffered(TestObject test, String fileName) throws IOException { RandomAccessFile raf = null; try { MemoryBuffer memoryBuffer = new MemoryBuffer(MAX_BUFFER_SIZE); raf = new RandomAccessFile(fileName, "rw"); test.write(memoryBuffer); raf.write(memoryBuffer.getBuffer()); } catch (IOException e) { if (raf != null) { raf.close(); } } }
TestObject写入方法如下:
public void write(MemoryBuffer unsafeBuffer) { unsafeBuffer.putLong(longVariable); unsafeBuffer.putLongArray(longArray); // we support nulls boolean objectExists = stringObject != null; unsafeBuffer.putBoolean(objectExists); if (objectExists) { unsafeBuffer.putCharArray(stringObject.toCharArray()); } objectExists = secondStringObject != null; unsafeBuffer.putBoolean(objectExists); if (objectExists) { unsafeBuffer.putCharArray(secondStringObject.toCharArray()); } }
直接内存缓冲区类(已简化了的,仅仅为了展示这个思想)
public class MemoryBuffer { // getting Unsafe by reflection public static final Unsafe unsafe = UnsafeUtil.getUnsafe(); private final byte[] buffer; private static final long byteArrayOffset = unsafe.arrayBaseOffset(byte[].class); private static final long longArrayOffset = unsafe.arrayBaseOffset(long[].class); /* other offsets */ private static final int SIZE_OF_LONG = 8; /* other sizes */ private long pos = 0; public MemoryBuffer(int bufferSize) { this.buffer = new byte[bufferSize]; } public final byte[] getBuffer() { return buffer; } public final void putLong(long value) { unsafe.putLong(buffer, byteArrayOffset + pos, value); pos += SIZE_OF_LONG; } public final long getLong() { long result = unsafe.getLong(buffer, byteArrayOffset + pos); pos += SIZE_OF_LONG; return result; } public final void putLongArray(final long[] values) { putInt(values.length); long bytesToCopy = values.length << 3; unsafe.copyMemory(values, longArrayOffset, buffer, byteArrayOffset + pos, bytesToCopy); pos += bytesToCopy; } public final 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; } /* other methods */ }
几个小时的Caliper测试结果如下:
Full trip [ns] | Standard deviation [ns] | |
Standard | 207307 | 2362 |
Standard on RAF | 42661 | 733 |
KRYO 1.x | 12027 | 112 |
KRYO 2.x | 11479 | 259 |
Unsafe | 8554 | 91 |
在最后我们可以得出一些结论:
最后,就像我们看到的那样,还是没有绝对的答案。对于我们中的大多数人来说,获得3000ns(0.003ms)的速度提升是不值得为每个需要序列化的对象来写单独实现的。在标准的方案中,我们大多数选择Kryo 。然而,在惜时如金的低延时系统中,这个选择将会是完全不同的。
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务