JIT预热后如何让ByteBuffer性能接近直接byte[]访问?
HeapByteBuffer性能低于自定义缓冲区的原因与优化疑问
优化简单解压缩例程时,发现一个奇怪的性能现象:手动实现的简易字节缓冲区,在单字节读取、多字节读取、判断流末尾这类简单操作上,比内置的HeapByteBuffer(堆内存及映射内存版本)快10%-20%。
测试的三种API
- ByteBuffer.wrap(byte[]) 方法
- 直接byte[]访问
- 模仿ByteBuffer API实现的简易字节访问包装类方法
简易包装类实现
class TestBuf { private final byte[] ary; private int pos = 0; public TestBuf(ByteBuffer buffer) { // 构造函数#1 ary = new byte[buffer.remaining()]; buffer.get(ary); } public TestBuf(byte[] inAry) { // 构造函数#2 ary = inAry; } public int readUByte() { return ary[pos++] & 0xFF; } public boolean hasRemaining() { return pos < ary.length; } public void get(byte[] out, int offset, int length) { System.arraycopy(ary, pos, out, offset, length); pos += length; } }
核心循环逻辑
while (buffer.hasRemaining()) { int op = buffer.readUByte(); if (op == 1) { int size = buffer.readUByte(); buffer.get(outputArray, outputPos, size); outputPos += size; } // ... 其他操作 }
测试组合
- native-array:直接将byte[]传入接收byte[]的方法(无复制)
- native-testbuf:将byte[]传入封装成TestBuf的方法(无复制,使用构造函数#2)
- native-buffer:将ByteBuffer.wrap(byte[])传入接收ByteBuffer的方法(无复制)
- buffer-array:将ByteBuffer.wrap(byte[])传入提取数组的方法
- buffer-testbuf:将ByteBuffer.wrap(byte[])传入通过TestBuf构造函数#1提取数组的方法
测试环境与结果
使用JMH(对每个outputArray做黑盒测试),在OpenJDK和GraalVM的Java 17环境下测试,预加载约5GiB解压缩语料库(包含约150,000个2KiB到15MiB的文件),每个语料库解压缩约10秒,测试有充分预热和迭代。结果在多台电脑上波动但趋势一致:
- GraalVM整体比OpenJDK慢10-15%,但性能排序一致
- native-array和native-testbuf速度最快,误差在0.5%以内(每语料库9.3秒)
- native-buffer始终最慢,比最快组合慢17-22%(每语料库11.4秒)
- buffer-array和buffer-testbuf处于中间,比native-array慢4-7%(每语料库9.7秒),但比native-buffer快15-17%
关键疑问点
- 通过ByteBuffer API使用包装后的byte[](native-buffer),比自定义简易包装类(native-testbuf)慢很多
- 完整复制数组的组合(buffer-*),仍比使用ByteBuffer.wrap对象(native-buffer)快很多
希望解答:
- 为何HeapByteBuffer在简单读取操作上比自定义实现慢?
- 如何更高效地使用HeapByteBuffer?
- 这种性能差异是否也适用于MappedByteBuffer?
更新
已发布完整基准测试、语料生成器和相关算法,近期发现删除无用代码或微调逻辑可让ByteBuffer性能接近原生数组,推测可能是偏移相关的内联缓存缺失导致。
内容的提问来源于stack exchange,提问作者byteit101
相关产品推荐
相关产品推荐

