【问题标题】:Efficiently send large int[] over sockets in Java在 Java 中通过套接字有效地发送大 int[]
【发布时间】:2012-09-07 14:23:01
【问题描述】:

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

这是迄今为止我尝试过的所有代码的代码,以及我正在测试的 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;
}

使用 ByteBuffer 和 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 中更有效地进行这种转换。请帮忙!

【问题讨论】:

  • 如果先刷新 outputstrem 会发生什么?
  • @tuğrulbüyükışık:我刚试了一下,当我在 objectoutputstream 之前刷新输出流时,它需要 50% 的时间:9 秒编辑:对不起,我走得很快,基准测试错误。其实没什么区别。
  • 将数据写入内存流而不是套接字需要多长时间?给你一些基线。这将为您提供编写基线并将其与通过套接字发送的隔离。
  • 我看到的一个问题是您正在创建一个大型目标数组,这在内存有限的设备上绝不是一件好事。我建议只使用 DataOutputStream 包装 BufferedOutputStream,而不是尝试自己进行转换。
  • @grieve - 与他目前使用的方法没有区别; DataOutputStream(相对于ObjectOutputStream)写入原始二进制数据。

标签: java android performance sockets casting


【解决方案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 编辑,因此对于循环的某些部分(或全部),它会解释字节码而不是执行本机指令。我对 Dalvik 了解得不够多,无法对此发表评论。

【讨论】:

  • 我同意运行您发布的循环可能会使我的处理器最大化。我想我真正的问题是:为什么 Java 没有比蛮力掩码和移位方法更好的方法来通过套接字发送 int[]?
  • 我很想说“因为没有魔法”,但这实际上是一系列的实现选择,其中一个是内存类型。是的,如果您使用 C,您可以创建一个缓冲区,使用 int* 写入该缓冲区,而不必担心将整数转换为字节。如果你有一个操作系统可以进行从进程内存到设备内存的 DMA 传输,你可以获得另一个提升。 Java 的目标是让您远离硬件。如果您需要接近硬件,那么您需要考虑一种可以让您到达那里的语言。
  • 您认为切换到 Android NDK 以在本机代码中进行转换和套接字传输会有所帮助吗?或者当我将巨大的 int[] 数组发送到本机代码时,我会遇到同样的问题吗?
  • 不知道。您仍然需要跨 Java/native 边界推送数据。我会更多地考虑更改我的代码,这样我就不必一次推送 500k 个值。或者指定更好的硬件。
  • 这实际上是一个研究项目,我试图找出使用慢速硬件处理大量数据的速度有多快,因此不能选择更小的阵列和更快的硬件:(
【解决方案2】:

IMO 从未打算像在 C 中那样高效地重新解释从 int[]byte[] 的内存区域。Java 甚至没有这样的内存地址模型。

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

例如这可能会比您的版本稍快(如果可以的话)

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;
}

【讨论】:

  • 我不一定需要将 int[] 转换为 byte[]。我只需要快速发送 int[],现在转换为 byte[] 是最快的。有没有办法在不转换的情况下发送 int[]?
  • OutputStream 是“字节的可写接收器”。没有办法直接发送 int 。所有子类都会在某个时候进行转换。
  • 我不明白为什么编写 int[] 的原始序列化字节(使用 ObjectOutputStream)比手动从 int[] 中提取字节数据要慢得多(正如您所建议的) .
  • ObjectOutputStream 的作用大致相同,但会在流上写入对象描述符等内容。这是不必要的额外工作。
  • @Jeremy: ObjectOutputStream 将数组的序列化版本写为 Java 对象,因此它包含更多信息,例如它的实现,因为接收者必须能够重建整个数组而无需知道它是一个 int 数组。
【解决方案3】:

我会这样做:

Socket senderSocket = new Socket(address, 4446);

OutputStream os = senderSocket.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(os);
DataOutputStream dos = new DataOutputStream(bos);

dos.writeInt(array.length);
for(int i : array) dos.writeInt(i);
dos.close();

另一方面,读起来像:

Socket recieverSocket = ...;
InputStream is = recieverSocket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
DataInputStream dis = new DataInputStream(bis);

int length = dis.readInt();
int[] array = new int[length];

for(int i = 0; i < length; i++) array[i] = dis.readInt();
dis.close();

【讨论】:

  • 我试过了,DataOutputStream 的性能几乎和 ObjectOutputStream 一样。
【解决方案4】:

如果您不反对使用库,您可能需要查看来自 Google 的 Protocol Buffers。它是为更复杂的对象序列化而构建的,但我敢打赌,他们努力研究如何在 Java 中快速序列化整数数组。

编辑:我查看了 Protobuf 源代码,它使用了类似于您的低级掩码和移位的东西。

【讨论】:

  • 不幸的是,我有点赶时间,这很复杂。有谁知道它是否会起作用?
  • 让我换个说法:“它会起作用”我的意思是我的性能至少会提高 25% 吗?
猜你喜欢
  • 2023-03-19
  • 1970-01-01
  • 2011-03-12
  • 1970-01-01
  • 2012-12-18
  • 1970-01-01
  • 2023-04-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多