重新解释底层位模式并将其写入数组或字段的最有效方法是什么?
What is the most efficient way to reinterpret underlying bit patterns and write it into and array or field?
使用Unsafe.putXXX
可以将基本类型放入数组或对象字段中。
但是像下面这样的代码会产生错误。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.arrayElementVarHandle(long[].class);
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
}
Exception in thread "main" java.lang.ClassCastException: Cannot cast [B to [J
at java.base/java.lang.Class.cast(Class.java:3780)
at Main.main(Main.java:15)
另外bytes
可以写成:
byte[] array = new byte[32];
long v = 5,
int i = 8;
int high = (int) (v >>> 32);
int low = (int) v;
array[i + 0] = (byte) (high >>> 24);
array[i + 1] = (byte) (high >>> 16);
array[i + 2] = (byte) (high >>> 8);
array[i + 3] = (byte) high;
array[i + 4] = (byte) (low >>> 24);
array[i + 5] = (byte) (low >>> 16);
array[i + 6] = (byte) (low >>> 8);
array[i + 7] = (byte) low;
有没有一种有效的方法来重新解释不同的类型并将它们写入字段和数组可能避免 Unsafe
但同样有效。
编译器或 JIT 将识别意图并进行相应优化的任何特殊情况。
对于 byte[]
特别是您可以使用 MethodHandles::byteArrayViewVarHandle
:
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
您必须使用 VarHandles 跳过一些障碍才能使它们与 Unsafe 一样快;
- 确保 VarHandle 本身是常量,这可以通过将其放入静态最终字段并从那里访问来完成。
- 确保 VarHandle 调用是准确的。这意味着
将第二个参数转换为 long,因为 VarHandle 除了 a
一样长。 (在最新的 JDK 中,您可以使用
VarHandle::withInvokeExactBehavior
来强制执行)。
这可以通过包装 VarHandle 集并在执行转换的辅助方法中调用来变得更容易:
private static final VarHandle LONG_ARR_HANDLE
= MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
public static void setLong(byte[] bytes, int index, long value) {
LONG_ARR_HANDLE.set(bytes, index, value);
}
public static long getLong(byte[] bytes, int index) {
return (long) LONG_ARR_HANDLE.get(bytes, index);
}
使用 ByteBuffer
的规范方法也是从 Java 开始最快的方法 17. 自己衡量(and/or 考虑其他选项)如果你不在 Java 17岁了。
ByteBuffer byteBuffer = ByteBuffer.allocate(bytesLength);
byteBuffer.asLongBuffer().put(longs);
return byteBuffer.array();
I did an experiment with serializing float[]
to byte[]
并根据所需的字节顺序和 Java 版本不同的方法出现不同,但 ByteBuffer
和 MemorySegment
和 Unsafe
或库使用它们是最重要的。 VarHandles 并没有落后太多,但它们仍然很循环。
在使用 G1 的 OpenJDK 17 上,在我的 MBP 16" 2019 上,结果是:
Benchmark (size) Mode Cnt Score Error Units
beByteBuffer 512 avgt 5 193.351 ± 21.366 ns/op
beByteBufferWrap 512 avgt 5 197.477 ± 43.743 ns/op
beDataOutputStream 512 avgt 5 1590.887 ± 60.744 ns/op
beKryo 512 avgt 5 861.624 ± 11.927 ns/op
beManualUnpacking 512 avgt 5 861.573 ± 10.108 ns/op
beObjectOutputStream 512 avgt 5 2168.386 ± 12.950 ns/op
beVarHandle 512 avgt 5 225.611 ± 1.839 ns/op
leByteBuffer 512 avgt 5 155.345 ± 1.229 ns/op
leByteBufferWrap 512 avgt 5 154.523 ± 0.853 ns/op
leDataOutputStream 512 avgt 5 55414.147 ± 9390.669 ns/op
leKryoUnsafe 512 avgt 5 156.019 ± 18.235 ns/op
leManualUnpacking 512 avgt 5 880.687 ± 12.856 ns/op
leMemorySegment 512 avgt 5 148.483 ± 0.897 ns/op
leUnsafeCopyMemory 512 avgt 5 149.107 ± 2.011 ns/op
leVarHandle 512 avgt 5 225.615 ± 1.357 ns/op
请注意,如果您尚未达到 Java 17,您的结果可能会大不相同! ObjectOutputStream
在 Java 17 和 two of the four *byteBuffer
variants were 2* as slow too 之前要慢得多。因此,请针对您的特定 JVM 和 JVM 配置进行测量。不过,总的来说,ByteBuffer
或 MemorySegment
是前进的方向。
使用Unsafe.putXXX
可以将基本类型放入数组或对象字段中。
但是像下面这样的代码会产生错误。
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.arrayElementVarHandle(long[].class);
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
}
Exception in thread "main" java.lang.ClassCastException: Cannot cast [B to [J
at java.base/java.lang.Class.cast(Class.java:3780)
at Main.main(Main.java:15)
另外bytes
可以写成:
byte[] array = new byte[32];
long v = 5,
int i = 8;
int high = (int) (v >>> 32);
int low = (int) v;
array[i + 0] = (byte) (high >>> 24);
array[i + 1] = (byte) (high >>> 16);
array[i + 2] = (byte) (high >>> 8);
array[i + 3] = (byte) high;
array[i + 4] = (byte) (low >>> 24);
array[i + 5] = (byte) (low >>> 16);
array[i + 6] = (byte) (low >>> 8);
array[i + 7] = (byte) low;
有没有一种有效的方法来重新解释不同的类型并将它们写入字段和数组可能避免 Unsafe
但同样有效。
编译器或 JIT 将识别意图并进行相应优化的任何特殊情况。
对于 byte[]
特别是您可以使用 MethodHandles::byteArrayViewVarHandle
:
public static void main(String[] args) {
VarHandle varHandle = MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
byte[] array = new byte[32];
printArray(array);
varHandle.set(array, 1, 5);
printArray(array);
System.out.println(varHandle.get(array, 1));
}
private static void printArray(byte[] array) {
System.out.println(Arrays.toString(array));
}
您必须使用 VarHandles 跳过一些障碍才能使它们与 Unsafe 一样快;
- 确保 VarHandle 本身是常量,这可以通过将其放入静态最终字段并从那里访问来完成。
- 确保 VarHandle 调用是准确的。这意味着
将第二个参数转换为 long,因为 VarHandle 除了 a
一样长。 (在最新的 JDK 中,您可以使用
VarHandle::withInvokeExactBehavior
来强制执行)。
这可以通过包装 VarHandle 集并在执行转换的辅助方法中调用来变得更容易:
private static final VarHandle LONG_ARR_HANDLE
= MethodHandles.byteArrayViewVarHandle(long[].class,
ByteOrder.nativeOrder());
public static void setLong(byte[] bytes, int index, long value) {
LONG_ARR_HANDLE.set(bytes, index, value);
}
public static long getLong(byte[] bytes, int index) {
return (long) LONG_ARR_HANDLE.get(bytes, index);
}
使用 ByteBuffer
的规范方法也是从 Java 开始最快的方法 17. 自己衡量(and/or 考虑其他选项)如果你不在 Java 17岁了。
ByteBuffer byteBuffer = ByteBuffer.allocate(bytesLength);
byteBuffer.asLongBuffer().put(longs);
return byteBuffer.array();
I did an experiment with serializing float[]
to byte[]
并根据所需的字节顺序和 Java 版本不同的方法出现不同,但 ByteBuffer
和 MemorySegment
和 Unsafe
或库使用它们是最重要的。 VarHandles 并没有落后太多,但它们仍然很循环。
在使用 G1 的 OpenJDK 17 上,在我的 MBP 16" 2019 上,结果是:
Benchmark (size) Mode Cnt Score Error Units
beByteBuffer 512 avgt 5 193.351 ± 21.366 ns/op
beByteBufferWrap 512 avgt 5 197.477 ± 43.743 ns/op
beDataOutputStream 512 avgt 5 1590.887 ± 60.744 ns/op
beKryo 512 avgt 5 861.624 ± 11.927 ns/op
beManualUnpacking 512 avgt 5 861.573 ± 10.108 ns/op
beObjectOutputStream 512 avgt 5 2168.386 ± 12.950 ns/op
beVarHandle 512 avgt 5 225.611 ± 1.839 ns/op
leByteBuffer 512 avgt 5 155.345 ± 1.229 ns/op
leByteBufferWrap 512 avgt 5 154.523 ± 0.853 ns/op
leDataOutputStream 512 avgt 5 55414.147 ± 9390.669 ns/op
leKryoUnsafe 512 avgt 5 156.019 ± 18.235 ns/op
leManualUnpacking 512 avgt 5 880.687 ± 12.856 ns/op
leMemorySegment 512 avgt 5 148.483 ± 0.897 ns/op
leUnsafeCopyMemory 512 avgt 5 149.107 ± 2.011 ns/op
leVarHandle 512 avgt 5 225.615 ± 1.357 ns/op
请注意,如果您尚未达到 Java 17,您的结果可能会大不相同! ObjectOutputStream
在 Java 17 和 two of the four *byteBuffer
variants were 2* as slow too 之前要慢得多。因此,请针对您的特定 JVM 和 JVM 配置进行测量。不过,总的来说,ByteBuffer
或 MemorySegment
是前进的方向。