Java:序列化到字节缓冲区的最快方法

Java:序列化到字节缓冲区的最快方法,java,arrays,performance,memory,serialization,Java,Arrays,Performance,Memory,Serialization,我需要用Java编写一个序列化库,它必须尽可能快。其思想是创建各种方法,这些方法将序列化指定的值及其关联的键,并将它们放入字节缓冲区中。必须创建几个包装此缓冲区的对象,因为需要序列化的对象可能很多 考虑事项: 我知道不安全类可能不会在每个JVM中实现,但这不是问题。 过早优化:这个库必须是快速的,而序列化是它唯一要做的事情。 序列化后的对象通常很小(小于10k),但它们很多,最大可达2Gb。 底层缓冲区可以扩展/减少,但我将跳过实现细节,该方法与ArrayList实现中使用的方法类似 澄清我的情

我需要用Java编写一个序列化库,它必须尽可能快。其思想是创建各种方法,这些方法将序列化指定的值及其关联的键,并将它们放入字节缓冲区中。必须创建几个包装此缓冲区的对象,因为需要序列化的对象可能很多

考虑事项: 我知道不安全类可能不会在每个JVM中实现,但这不是问题。 过早优化:这个库必须是快速的,而序列化是它唯一要做的事情。 序列化后的对象通常很小(小于10k),但它们很多,最大可达2Gb。 底层缓冲区可以扩展/减少,但我将跳过实现细节,该方法与ArrayList实现中使用的方法类似

澄清我的情况:我有各种方法,比如

public void putByte(short key, byte value);
public void putInt(short key, int value);
public void putFloat(short key, float value);

... and so on...
这些方法将键和值附加到字节流中,因此如果调用putInt(-11234567890),我的缓冲区将如下所示:(流是big-endian)

最后,必须调用toBytes()之类的方法来返回一个字节数组,该数组是底层缓冲区的修剪(如果需要)版本

现在,我的问题是:在java中实现这一点最快的方法是什么

我在谷歌上搜索并偶然发现了不同的页面(其中一些页面是如此),我也做了一些基准测试(但我在基准测试方面并没有真正的经验,这也是我请求更有经验的程序员对此主题提供帮助的原因之一)

我提出了以下解决方案:

1-最直接的:字节数组

如果我必须序列化一个int,它将如下所示:

public void putInt(short key, int value)
{
    array[index]   = (byte)(key >> 8);
    array[index+1] = (byte) key;
    array[index+2] = (byte)(value >> 24);
    array[index+3] = (byte)(value >> 16);
    array[index+4] = (byte)(value >> 8);
    array[index+5] = (byte) value;
}
2-字节缓冲区(直接或字节数组包装)

putInt方法如下所示

public void putInt(short key, int value)
{
   byteBuff.put(key).put(value);
}
3-通过不安全的本机内存上分配

使用不安全类,我会在本机内存上分配缓冲区,因此putInt看起来像:

public void putInt(short key, int value)
{
  Unsafe.putShort(address, key);
  Unsafe.putInt(address+2, value);
}
4-通过新字节[]分配,通过不安全访问

我在java编写的lz4压缩库中看到了这种方法。基本上,在实例化字节数组后,我会按以下方式写入字节:

public void putInt(short key, int value)
{
   Unsafe.putShort(byteArray, BYTE_ARRAY_OFFSET + 0, key);
   Unsafe.putInt(byteArray, BYTE_ARRAY_OFFSET + 2, value);
}
这里的方法是简化的,但基本思想是所示的,我还必须实现getter方法。现在,自从我开始在这里工作以来,我学到了以下几点:

1-如果安全,JVM可以删除数组边界检查(例如,在for循环中,计数器必须小于数组的长度) 2-跨越JVM内存边界(从本机内存读/写/到本机内存)是有成本的。 3-调用本机方法可能会有成本。 4-不安全的Putter和Getter不会在本机内存或常规数组中进行边界检查。 5字节缓冲区包装一个字节数组(非直接)或一个纯本机内存区域(直接),因此案例2内部看起来像案例1或3

我运行了一些基准测试(但正如我所说,我希望其他开发人员的意见/经验),案例4的阅读速度似乎略高于案例1(几乎等于),而写作速度大约是案例1的3倍。另外,使用不安全读写(案例4)的for循环将一个数组复制到另一个数组(一次复制8个字节)似乎比System.arraycopy更快

长话短说(很抱歉写了这么长的帖子):

案例1似乎速度很快,但这样我每次都要写一个字节+屏蔽操作,这让我觉得可能不安全,即使是调用本机代码也可能更快

案例2与案例1和案例3相似,因此我可以跳过它(如果我遗漏了什么,请纠正我)

情况3似乎是最慢的(至少从我的基准测试来看是如此),而且,我需要从本机内存复制到字节数组,因为这必须是输出。但这位程序员声称这是迄今为止最快的方法。如果我理解正确,我遗漏了什么

案例4(如支持)似乎是最快的

选择的数量和一些相互矛盾的信息让我有点困惑,所以有人能澄清我的疑问吗

我希望我写下了所有需要的信息,否则就要求澄清


提前感谢。

案例5:
DataOutputStream
通过tearrayoutputstream写入

赞成:已经做了;它和你在这里提到的任何东西一样快;所有原语都已实现。反之,则是从ByteArrayInputStream读取数据输入流


缺点:我想不出什么。

不使用现有序列化库的任何特定原因,例如?感谢您指出这一点。但是,我不想依赖外部库,因为序列化协议主要用于基本数据类型和已经序列化的数据类型(字节、短整数数组…)。使用Kryo可能有点过头了。另一点是,幸运的是,我可以自由地进行实验,因此,即使我正在重新发明轮子,这至少对我来说是一个有用的练习。序列化数据的目标介质是什么?序列化到字节缓冲区本身不是很有用。除非您正在做一些真正有趣的事情,例如RDMA,否则序列化到大多数目标(网络、磁盘等)是I/O绑定的。如果您只是在本地执行IPC,我会首先考虑重构应用程序的可能性,以完全避免序列化/反序列化。你能提供一些关于你正在做什么的细节,以及什么需要这样的性能关注点吗?如果你没有基准测试的经验,要知道这是相当困难的。无论如何,使用或,否则你的结果几乎肯定是垃圾。我列出了基准测试可能产生误导性结果的几个原因。您的基准测试似乎遵循类似的反模式。我看到了ByteArrayOutputStream的源代码,基本上
public void putInt(short key, int value)
{
   Unsafe.putShort(byteArray, BYTE_ARRAY_OFFSET + 0, key);
   Unsafe.putInt(byteArray, BYTE_ARRAY_OFFSET + 2, value);
}