将长数组转换为字节数组的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.

java中是否有任何本机方法可以将长数组复制/转换为字节数组,反之亦然。我知道下面的方法

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()
  • JNI+
  • 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