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

Mas*_*ldo 6 java memory arrays performance serialization

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

注意事项:我知道 Unsafe 类可能不会在每个 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...
Run Code Online (Sandbox Code Playgroud)

这些方法将键和值附加到字节流中,因此如果我调用 putInt(-1, 1234567890) 我的缓冲区将如下所示:(流是大端)

     key       the integer value  
[0xFF, 0xFF, 0x49, 0x96, 0x02, 0xD2]
Run Code Online (Sandbox Code Playgroud)

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

现在,我的问题是:在 Java 中执行此操作的最快方法是什么?

我在谷歌上搜索并偶然发现了各种页面(其中一些在 SO 上),我也做了一些基准测试(但我在基准测试方面并没有真正的经验,这就是我向更有经验的程序员寻求帮助的原因之一话题)。

我想出了以下解决方案:

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;
}
Run Code Online (Sandbox Code Playgroud)

2- ByteBuffer(直接或字节数组包装器)

putInt 方法如下所示

public void putInt(short key, int value)
{
   byteBuff.put(key).put(value);
}
Run Code Online (Sandbox Code Playgroud)

3-通过 Unsafe本机内存上分配

使用 Unsafe 类,我将在本机内存上分配缓冲区,因此 putInt 将如下所示:

public void putInt(short key, int value)
{
  Unsafe.putShort(address, key);
  Unsafe.putInt(address+2, value);
}
Run Code Online (Sandbox Code Playgroud)

4- 通过new byte[]分配,通过Unsafe访问

我在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);
}
Run Code Online (Sandbox Code Playgroud)

这里的方法被简化了,但基本思想是所示的,我还必须实现 getter 方法。现在,自从我开始从事这项工作以来,我学到了以下几点:

1- JVM 可以删除数组边界检查是否安全(例如在 for 循环中,计数器必须小于数组的长度) 2- 跨越 JVM 内存边界(从/向本机内存读取/写入)有成本。3- 调用本机方法可能会产生成本。4- 不安全的推杆和吸气剂不会在本机内存中进行边界检查,也不会在常规数组上进行。5- ByteBuffers 包装了一个字节数组(非直接)或一个普通的本机内存区域(直接),因此案例 2 在内部看起来像案例 1 或 3。

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

长话短说(对不起,长篇文章):

case 1似乎很快,但是这样我每次都必须写一个字节 + 屏蔽操作,这让我认为可能不安全,即使它是对本机代码的调用可能会更快。

案例 2类似于案例 1 和 3,所以我可以跳过它(如果我遗漏了什么,请纠正我)

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

案例 4此处支持)似乎是最快的。

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

我希望我写了所有需要的信息,否则只是要求澄清。

提前致谢。

use*_*421 1

案例5:DataOutputStream写信给aByteArrayOutputStream.

Pro:已经完成了;它和你在这里提到的其他任何东西一样快;所有原语都已实现。相反的是 DataInputStream 从 ByteArrayInputStream 读取。

缺点:我想不出什么。

  • @EJP 通常我会同意你的观点,*但是*在这种情况下,性能是一个重要的考虑因素。BAOS 的性能问题: 1. 用户需要分配一个新的 BAOS,这将创建一个新的 byte[]。重新使用现有的 byte[] 会更加高效。2. BAOS.toByteArray() 是同步的 - 如果锁定不是问题,那只是开销。3. BAOS.toByteArray() 实际上将结果复制到一个新的 byte[] 中 - 因此我们最终得到*两个* byte[] 创建,以及一个数组副本。 (2认同)