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(此处支持)似乎是最快的。
选择的数量和一些相互矛盾的信息让我有点困惑,所以有人能澄清我这些疑问吗?
我希望我写了所有需要的信息,否则只是要求澄清。
提前致谢。
案例5:DataOutputStream写信给aByteArrayOutputStream.
Pro:已经完成了;它和你在这里提到的其他任何东西一样快;所有原语都已实现。相反的是 DataInputStream 从 ByteArrayInputStream 读取。
缺点:我想不出什么。