14

私は、ある Android 携帯電話から別の Android 携帯電話にソケット接続を介して 500,000 の整数の配列をできるだけ早く送信する必要がある Java アプリケーションに取り組んでいます。主なボトルネックは、ObjectOutputStreams、ByteBuffers、または低レベルのマスクとシフトの変換を使用するかどうかに関係なく、ソケットが整数を取得できるように整数を変換しているようです。あるJavaアプリから別の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 秒 (writeObject() の代わりに DataOutPutStream と writeInt() を使用してこのバリエーションを試しましたが、大きな違いはありませんでした)

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 でこの変換をより効率的に行う方法が必要です。助けてください!

4

4 に答える 4

5

コメントで指摘したように、プロセッサの限界にぶつかっていると思います。これは他の人に役立つかもしれないので、私はそれを分解します。整数をバイトに変換するループは次のとおりです。

    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 プロセッサは、1 秒あたり約 6 億回の操作を処理できます。したがって、ループの各反復は、すべての操作で約 1/1200 秒を消費します。

繰り返しますが、非常に大まかな数値を使用すると (ARM 命令セットがわからないため、アクションごとに多かれ少なかれある可能性があります)、操作カウントは次のとおりです。

  • テスト/分岐: 5 (カウンターの取得、配列の長さの取得、比較、分岐、カウンターのインクリメント)
  • マスクとシフト: 10 x 4 (カウンターの取得、入力配列ベースの取得、加算、マスクの取得、シフト、カウンターの乗算、オフセットの加算、出力ベースへの加算、ストア)

大まかな数字で言うと、このループにかかる時間はせいぜい55/1200 秒、つまり 0.04 秒です。ただし、最良のシナリオを扱っているわけではありません。1 つには、配列がこれほど大きくなると、プロセッサ キャッシュのメリットが得られないため、すべての配列のストアとロードに待機状態を導入します。

さらに、ここで説明した基本的な操作は、直接マシン コードに変換される場合とされない場合があります。そうでない場合 (そして私はそうではないと思います)、ループは私が説明したよりも多くのコストがかかります。

最後に、本当に運が悪い場合、JVM はコードを JIT していないため、ループの一部 (またはすべて) でネイティブ命令を実行するのではなく、バイトコードを解釈しています。私はそれについてコメントするのに十分なほど Dalvik について知りません。

于 2012-09-07T15:09:14.873 に答える
1

int[]Java は、C でできるようにメモリ領域を からに効率的に再解釈できるようにすることを意図したもの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;
}
于 2012-09-07T14:42:36.353 に答える
1

私は次のようにします:

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();
于 2012-09-07T15:03:04.753 に答える
1

ライブラリの使用に抵抗がない場合は、Google のProtocol Buffersをチェックしてみてください。これは、はるかに複雑なオブジェクトのシリアル化用に構築されていますが、Java で整数の配列をすばやくシリアル化する方法を見つけるために懸命に取り組んだに違いありません。

編集: Protobuf のソース コードを調べたところ、低レベルのマスクとシフトに似たものを使用しています。

于 2012-09-07T14:55:59.040 に答える