![最长公共子序列](https://img-blog.csdnimg.cn/img_convert/b50c553094639ac22a34cfdb7993c2b9.png)
最长公共子序列
为什么我们需要快速紧凑的序列化? 我们的许多系统都是分布式的,我们需要通过在流程之间高效地传递状态进行通信。 这种状态存在于我们的物体内部。 我已经介绍了许多系统,通常大部分成本是该状态与字节缓冲区之间的串行化。 我已经看到用于实现此目的的大量协议和机制。 一方面,是易于使用但效率低下的协议,例如Java序列化, XML和JSON 。 另一方面,二进制协议可以非常快速和高效,但需要更深入的理解和技能。
在本文中,我将说明使用简单的二进制协议时可能实现的性能提升,并介绍一种Java中可用的鲜为人知的技术,以实现与C或C ++之类的本地语言类似的性能。
要比较的三种方法是:
- Java序列化:Java中有一个对象实现Serializable的标准方法。
- 通过ByteBuffer进行二进制:使用ByteBuffer API的简单协议,以二进制格式写入对象的字段。 这是我们认为是好的二进制编码方法的基准。
- 二进制通过不安全:介绍不安全和其允许直接内存操作方法的集合。 在这里,我将展示如何获得与C / C ++类似的性能。
编码
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
分析
使用Java序列化在我的快速2.4 GHz Sandy Bridge笔记本电脑上写和读一个相对较小的对象大约需要10,000ns,而使用Unsafe时,即使考虑到测试代码本身,也可以减少到不到100ns。 为了说明这一点,在使用Java序列化时,成本与网络跃点相当! 如果您的传输是同一系统上的快速IPC机制,那么这将是非常昂贵的。
Java序列化如此昂贵的原因有很多。 例如,它为每个对象写出完全限定的类和字段名称以及版本信息。 同样, ObjectOutputStream保留所有书面对象的集合,以便在调用close()时可以将它们合并。 对于此示例对象,Java序列化需要340字节,但是对于二进制版本,我们仅需要185字节。 Java序列化格式的详细信息可以在这里找到。 如果我没有使用数组存储大部分数据,那么由于字段名的原因,使用Java序列化,序列化的对象会大很多。 以我的经验,基于文本的协议(例如XML和JSON)甚至可能比Java序列化的效率更低。 还应注意Java序列化是RMI所采用的标准机制。
真正的问题是要执行的指令数量。 Unsafe方法有很大优势,因为在Hotspot和许多其他JVM中,优化器将这些操作视为内部操作,并用汇编指令替换了调用以执行内存操作。 对于基本类型,这将导致单个x86 MOV指令,该指令通常可以在单个周期内发生。 如我在上一篇文章中所述,可以通过让Hotspot输出优化的代码来看到细节。
现在必须说,“功能强大,责任重大”,如果您使用Unsafe,它实际上与C语言编程相同,并且当偏移量错误时,也会发生内存访问冲突。
添加一些上下文
“ Google协议缓冲区之类的怎么样?”,我听说您大声疾呼。 这些是非常有用的库,通常可以比Java序列化提供更好的性能和更大的灵活性。 但是,它们与我在此处显示的使用Unsafe的性能差距不大。 协议缓冲区解决了一个不同的问题,并提供了很好的自描述消息,这些消息在各种语言之间都可以正常工作。 请使用不同的协议和序列化技术进行测试以比较结果。
另外你之间的精明会问,“什么字节顺序的整数(字节顺序)写的?” 使用不安全时,字节以本机顺序写入。 这对于IPC以及相同类型的系统之间非常有用。 如果系统使用不同的格式,则必须进行转换。
我们如何处理一个类的多个版本或如何确定对象所属的类? 我想让本文重点关注,但让我们说一个简单的整数来表示实现类是标题所需的全部。 该整数可用于查找反序列化操作的适当实现。
我经常听到反对二进制协议和文本协议的争论,那么对于人类可读和调试该怎么办? 有一个简单的解决方案。 开发用于读取二进制格式的工具!
结论
总而言之,通过有效使用相同的技术,可以实现与Java中相同的本机C / C ++性能级别,从而可以在字节流之间来回串行化对象。 我为其提供了基本实现的UnsafeMemory类可以轻松地扩展为封装此行为,从而在使用这种敏锐的工具时保护自己免受许多潜在问题的困扰。
现在是一个亟待解决的问题。 如果Java通过本地提供我对Unsafe所做的有效工作来为Serializable提供替代的Marshallable接口,会不会更好呢???
参考:来自Mechanical Sympathy博客的JCG合作伙伴Martin Thompson提供的Java对象序列化的本 机C / C ++类性能,适用于Java对象序列化。
翻译自: https://www.javacodegeeks.com/2012/07/native-cc-like-performance-for-java.html
最长公共子序列