将长数组转换为字节数组的Java本机方法
java中是否有任何本机方法可以将长数组复制/转换为字节数组,反之亦然。我知道下面的方法将长数组转换为字节数组的Java本机方法,java,performance,java-io,Java,Performance,Java Io,java中是否有任何本机方法可以将长数组复制/转换为字节数组,反之亦然。我知道下面的方法 ByteBuffer bb = ByteBuffer.allocate(longArray.length * Long.BYTES); bb.asLongBuffer().put(longArray); return bb.array(); 但是上述方法非常慢,特别是当我们的Java应用程序处理大量is数据时 System.arraycopy是复制相同类型数组的一种出色性能。如果java声称system.
ByteBuffer bb = ByteBuffer.allocate(longArray.length * Long.BYTES);
bb.asLongBuffer().put(longArray);
return bb.array();
但是上述方法非常慢,特别是当我们的Java应用程序处理大量is数据时
System.arraycopy是复制相同类型数组的一种出色性能。如果java声称system.arraycopy使用的是本机c方法,那么为什么它们不包括将long/int数组复制到字节数组,就像在c memcpy中那样
谢谢你的帮助
谢谢。ByteBuffer.asLongBuffer().put()是在不同数组类型之间进行复制的正确方法。它是简单、纯Java的,速度也不慢。我将在下面演示 请注意,要使结果等效于
memcpy
,需要将ByteBuffer
切换为本机字节顺序。默认情况下,ByteBuffers是BIG_ENDIAN,而x86体系结构是LITTLE_ENDIAN。切换到本机字节顺序也会加快复制速度
bb.order(ByteOrder.nativeOrder());
还有一些其他方法可以转换数组,值得一提
Unsafe.copyMemory()
sun.misc.Unsafe
是一个JDK私有的、不受支持的和不推荐使用的API,但它仍然适用于所有版本,至少从JDK 6到JDK 14。它的优点是它是JavaAPI——不需要创建本机库
相反,JNI函数需要加载本机库,但这些函数是标准的,并且受支持。GetPrimitiveArrayCritical
+SetByteArrayRegion
的组合允许将数据直接从一个阵列复制到另一个阵列,而无需中间存储
HotSpot JVM还有一个未记录的扩展-,它允许直接从本机代码访问Java基元数组,而无需JNI开销。但请记住,依赖未记录的API会使代码不可移植。好消息是,关键的本机方法与常规的本机方法相比较,也就是说,当您同时实现这两种方法时,您可以确保代码在任何地方都能工作
性能如何?
我创建了一个基准来比较所有讨论的技术
包装工作台;
导入org.openjdk.jmh.annotations.*;
导入sun.misc.Unsafe;
导入java.lang.reflect.Field;
导入java.nio.ByteBuffer;
导入java.nio.ByteOrder;
@国家(范围、基准)
公共类LongaryCopy{
@参数({“100”、“1000”、“10000”})
私有整数大小;
专用长[]长数组;
@设置
公共作废设置(){
longArray=新长[大小];
}
@基准
公共字节[]字节缓冲(){
ByteBuffer bb=ByteBuffer.allocate(longArray.length*Long.BYTES);
bb.order(ByteOrder.nativeOrder());
bb.asLongBuffer().put(长数组);
返回bb.array();
}
@基准
公共字节[]jni(){
byte[]byteArray=新字节[longArray.length*Long.BYTES];
副本(长数组、字节数组、字节数组.length);
乘火车返回;
}
@基准
公共字节[]jniCritical(){
byte[]byteArray=新字节[longArray.length*Long.BYTES];
copyCritical(longArray、byteArray、byteArray.length);
乘火车返回;
}
@基准
公共字节[]不安全(){
byte[]byteArray=新字节[longArray.length*Long.BYTES];
不安全的.copyMemory(长数组,不安全的.ARRAY\u长\u基\u偏移,
byteArray,不安全的.ARRAY\u BYTE\u BASE\u OFFSET,
byteArray.长度);
乘火车返回;
}
私有静态本机无效副本(长[]src,字节[]dst,int size);
私有静态本机void copyricial(长[]src,字节[]dst,int size);
私有静态最终不安全;
静止的{
试一试{
字段f=不安全的.class.getDeclaredField(“不安全的”);
f、 setAccessible(true);
theUnsafe=(Unsafe)f.get(null);
}捕获(例外e){
抛出新的运行时异常(e);
}
系统加载库(“arraycopy”);
}
}
arraycopy.c
#include <jni.h>
#include <string.h>
JNIEXPORT void Java_bench_LongArrayCopy_copy(JNIEnv* env, jobject unused,
jlongArray src, jbyteArray dst, jint size) {
void* data = (*env)->GetPrimitiveArrayCritical(env, src, NULL);
(*env)->SetByteArrayRegion(env, dst, 0, size, (jbyte*)data);
(*env)->ReleasePrimitiveArrayCritical(env, src, data, JNI_COMMIT);
}
JNIEXPORT void Java_bench_LongArrayCopy_copyCritical(JNIEnv* env, jobject unused,
jlongArray src, jbyteArray dst,
jint size) {
Java_bench_LongArrayCopy_copy(env, unused, src, dst, size);
}
JNIEXPORT void JavaCritical_bench_LongArrayCopy_copyCritical(jint srclen, jlong* src,
jint dstlen, jbyte* dst,
jint size) {
memcpy(dst, src, size);
}
与其他方法相比,ByteBuffer可能要慢一些。然而,自JDK 9以来,ByteBuffer的性能已经得到了极大的优化。如果我们在现代JDK(11或14)上运行相同的示例,我们将看到ByteBuffer实际上是最快的方法
JDK14.0.1
Benchmark (size) Mode Cnt Score Error Units
LongArrayCopy.byteBuffer 1000 avgt 10 566,038 ± 1,010 ns/op
LongArrayCopy.jni 1000 avgt 10 659,575 ± 2,145 ns/op
LongArrayCopy.jniCritical 1000 avgt 10 575,381 ± 2,283 ns/op
LongArrayCopy.unsafe 1000 avgt 10 602,838 ± 4,587 ns/op
ByteBuffer怎么能比不安全更快?诀窍在于JVM编译器可以矢量化、展开和内联ByteBuffer的复制循环,而
不安全。copyMemory
始终调用JVM运行时。没有更有效的方法将数组转换为数组。有很多方法可以进行其他类型的转换,例如ByteBuffer.asLongBuffer()
,它根本不进行复制。(这就是说,我很好奇您将这种方法描述为“非常非常慢”——是什么导致您得出这样的结论?@JohannesKuhn为什么ByteBuffer.allocate()
会很慢?请注意,此问题涉及的是常规的字节缓冲区,而不是直接或映射的缓冲区。
Benchmark (size) Mode Cnt Score Error Units
LongArrayCopy.byteBuffer 1000 avgt 10 566,038 ± 1,010 ns/op
LongArrayCopy.jni 1000 avgt 10 659,575 ± 2,145 ns/op
LongArrayCopy.jniCritical 1000 avgt 10 575,381 ± 2,283 ns/op
LongArrayCopy.unsafe 1000 avgt 10 602,838 ± 4,587 ns/op