在 Java 中通过套接字高效发送大型 int[]

2022-09-04 04:10:19

我正在开发一个Java应用程序,我需要尽快通过套接字连接将500,000个整数的数组从一部Android手机发送到另一部Android手机。主要的瓶颈似乎是转换整数,以便套接字可以获取它们,无论我使用ObjectOutputStreams,ByteBuffers还是低级掩码和移位转换。通过套接字将int[]从一个Java应用程序发送到另一个应用程序的最快方法是什么?

以下是我迄今为止尝试过的所有内容的代码,以及我正在测试的LG Optimus V(600 MHz ARM处理器,Android 2.2)的基准测试。

低电平遮罩和移位:0.2 秒

public static byte[] intToByte(int[] input)
{
    byte[] output = new byte[input.length*4];

    for(int i = 0; i < input.length; i++) {
        output[i*4] = (byte)(input[i] & 0xFF);
        output[i*4 + 1] = (byte)((input[i] & 0xFF00) >>> 8);
        output[i*4 + 2] = (byte)((input[i] & 0xFF0000) >>> 16);
        output[i*4 + 3] = (byte)((input[i] & 0xFF000000) >>> 24);
    }

    return output;
}

使用字节缓冲器和 IntBuffer:0.75 秒

public static byte[] intToByte(int[] input)
{
    ByteBuffer byteBuffer = ByteBuffer.allocate(input.length * 4);        
    IntBuffer intBuffer = byteBuffer.asIntBuffer();
    intBuffer.put(input);

    byte[] array = byteBuffer.array();

    return array;
}

ObjectOutputStream:3.1秒(我尝试使用DataOutPutStream和writeInt()而不是writeObject()来改变它,但它并没有太大的区别)

public static void sendSerialDataTCP(String address, int[] array) throws IOException
{
    Socket senderSocket = new Socket(address, 4446);

    OutputStream os = senderSocket.getOutputStream();
    BufferedOutputStream  bos = new BufferedOutputStream (os);
    ObjectOutputStream oos = new ObjectOutputStream(bos);
    oos.writeObject(array);

    oos.flush();
    bos.flush();
    os.flush();
    oos.close();
    os.close();
    bos.close();

    senderSocket.close();
}

最后,我用来发送byte[]的代码:在intToByte()函数上加了0.2秒

public static void sendDataTCP(String address, byte[] data) throws IOException
{
    Socket senderSocket = new Socket(address, 4446);

    OutputStream os = senderSocket.getOutputStream();
    os.write(data, 0, data.length);
    os.flush();

    senderSocket.close();
}

我正在套接字的两端编写代码,因此我可以尝试任何类型的字节序,压缩,序列化等。必须有一种方法可以在Java中更有效地进行这种转换。请帮忙!


答案 1

正如我在评论中指出的那样,我认为您正在挑战处理器的极限。由于这可能对其他人有帮助,因此我将对其进行分解。下面是将整数转换为字节的循环:

    for(int i = 0; i < input.length; i++) {
        output[i*4] = (byte)(input[i] & 0xFF);
        output[i*4 + 1] = (byte)((input[i] & 0xFF00) >>> 8);
        output[i*4 + 2] = (byte)((input[i] & 0xFF0000) >>> 16);
        output[i*4 + 3] = (byte)((input[i] & 0xFF000000) >>> 24);
    }

此循环执行 500,000 次。600Mhz 处理器每秒可以处理大约 600,000,000 个操作。因此,循环的每次迭代将为每个操作消耗大约1/1200秒。

同样,使用非常粗略的数字(我不知道ARM指令集,因此每个操作可能或多或少),这是一个操作计数:

  • 测试/分支:5(检索计数器、检索数组长度、比较、分支、递增计数器)
  • 掩码和移位:10 x 4(检索计数器,检索输入数组基数,添加,检索掩码,以及移位,乘法计数器,添加偏移量,添加到输出基数,存储)

好吧,所以粗略地说,这个循环最多需要55/1200秒,或0.04秒。但是,您没有处理最佳情况。首先,对于如此大的数组,您将不会从处理器缓存中受益,因此您将在每个数组存储和加载中引入等待状态。

另外,我描述的基本操作可能会也可能不会直接转换为机器代码。如果不是(我怀疑不是),循环的成本将比我描述的要高。

最后,如果你真的运气不好,JVM没有JIT你的代码,所以对于循环的某些部分(或全部),它是解释字节码而不是执行本机指令。我对达尔维克的了解还不够多,无法对此发表评论。


答案 2

Java从来就不是为了能够有效地重新解释一个内存区域,就像你在C中所做的那样。它甚至没有这样的内存地址模型。int[]byte[]

您要么需要使用本机来发送数据,要么可以尝试找到一些微优化。但我怀疑你会收获很多。

例如,这可能比您的版本稍快(如果它工作的话)

public static byte[] intToByte(int[] input)
{
    byte[] output = new byte[input.length*4];

    for(int i = 0; i < input.length; i++) {
        int position = i << 2;
        output[position | 0] = (byte)((input[i] >>  0) & 0xFF);
        output[position | 1] = (byte)((input[i] >>  8) & 0xFF);
        output[position | 2] = (byte)((input[i] >> 16) & 0xFF);
        output[position | 3] = (byte)((input[i] >> 24) & 0xFF);
    }
    return output;
}

推荐