web-dev-qa-db-ja.com

JNI-Javaとネイティブコードの間で大量のデータを渡す

私は次のことを達成しようとしています:

1)画像を表すJava側にバイト配列があります。

2)ネイティブコードにアクセスを許可する必要があります。

3)ネイティブコードは、GraphicsMagickを使用してこの画像をデコードし、resizeを呼び出して一連のサムネイルを作成します。また、ベクトルまたはunint8_t配列である画像の知覚ハッシュも計算します。

4)このデータをJava側に返すと、別のスレッドがそれを読み取ります。サムネイルは、HTTP経由で外部ストレージサービスにアップロードされます。

私の質問は:

1)Javaから私のネイティブコードにバイトを渡す最も効率的な方法は何ですか?バイト配列としてアクセスできます。ここでバイト配列とバイトバッファ(このバイト配列をラップする)を渡した場合と比べて、特に利点はありません。

2)これらのサムネイルと知覚ハッシュをJavaコードに戻す最良の方法は何ですか?私はいくつかのオプションを考えました:

(i)Javaにバイトバッファを割り当て、それをネイティブメソッドに渡すことができます。ネイティブメソッドは、書き込み後に制限を設定し、書き込まれたバイト数または成功を示すブール値を返すことができます。次に、バイトバッファーをスライスしてダイシングして、個別のサムネイルと知覚ハッシュを抽出し、サムネイルをアップロードする別のスレッドに渡します。このアプローチの問題は、割り当てるサイズがわからないことです。必要なサイズは、事前にわからない生成されたサムネイルのサイズとサムネイルの数によって異なります(これは事前にわかっています)。

(ii)必要なサイズがわかったら、ネイティブコードでバイトバッファーを割り当てることもできます。カスタムパッキングプロトコルに基づいてblobを正しい領域にmemcpyし、このバイトバッファーを返すことができます。 (i)と(ii)の両方は、各サムネイルの長さと知覚ハッシュを示す必要のあるカスタムパッキングプロトコルのため、複雑に見えます。

(iii)サムネイル用のフィールドがあるJavaクラスを定義します:バイトバッファーの配列と知覚ハッシュ:バイト配列。必要な正確なサイズがわかっている場合は、ネイティブコードでバイトバッファーを割り当てることができます。次に、GraphicsMagick blobから各バイトバッファーの直接アドレスにバイトをmemcpyできます。 Javaコードがバイトバッファの大きさを認識できるように、バイトバッファに書き込まれるバイト数を設定する方法もあると想定しています。バイトバッファーが設定された後、Javaオブジェクトを入力して返すことができます。 (i)と(ii)と比較して、ここでバイトバッファーをさらに作成し、Javaオブジェクトも作成しますが、カスタムプロトコルの複雑さを回避します。 (i)、(ii)、および(iii)の根拠-これらのサムネイルを使用して行うのはそれらをアップロードすることだけなので、NIO経由でアップロードするときに、バイトバッファー(バイト配列に対して)で追加のコピーを保存することを望んでいました。

(iv)サムネイル用の(バイトバッファーの代わりに)バイト配列の配列と、知覚ハッシュ用のバイト配列を持つJavaクラスを定義します。これらのJava配列をネイティブコードで作成し、SetByteArrayRegionを使用してGraphicsMagickブロブからバイトをコピーします。以前の方法と比較した場合の欠点は、アップロード時にこのバイト配列をヒープから直接バッファーにコピーすると、Javaランドにさらに別のコピーが存在することです。ここでも(iii)と比較して複雑さの点で何かを節約できるかどうかはわかりません。

どんなアドバイスでも素晴らしいでしょう。

編集:@mainは興味深い解決策を提案しました。そのオプションをフォローアップするために質問を編集しています。 @mainの提案のようにDirectBufferでネイティブメモリをラップしたい場合、ネイティブメモリを安全に解放できるタイミングを知るにはどうすればよいですか?

27
Rajiv

Javaから私のネイティブコードにバイトを渡す最も効率的な方法は何ですか?バイト配列としてアクセスできます。バイト配列として渡すことに特別な利点はありません。バイトバッファ(このバイト配列をラップする)とここのバイト配列。

直接ByteBufferの大きな利点は、ネイティブ側で GetDirectByteBufferAddress を呼び出すことができ、オーバーヘッドなしでバッファーの内容へのポインターをすぐに取得できることです。バイト配列を渡す場合は、GetByteArrayElementsおよびReleaseByteArrayElements(配列をコピーする場合があります)または重要なバージョン(GCを一時停止する)を使用する必要があります。したがって、直接ByteBufferを使用すると、コードのパフォーマンスにプラスの影響を与える可能性があります。

あなたが言ったように、(i)メソッドが返すデータの量がわからないので機能しません。 (ii)そのカスタムパッケージプロトコルのために複雑すぎます。私は(iii)の変更されたバージョンに行きます:そのオブジェクトは必要ありません。最初の要素がハッシュで、他の要素がサムネイルであるByteBuffersの配列を返すことができます。そして、memcpys をすべて破棄できます。これが直接のByteBufferの要点です:コピーの回避。

コード:

void Java_MyClass_createThumbnails(JNIEnv* env, jobject, jobject input, jobjectArray output)
{
    jsize nThumbnails = env->GetArrayLength(output) - 1;
    void* inputPtr = env->GetDirectBufferAddress(input);
    jlong inputLength = env->GetDirectBufferCapacity(input);

    // ...

    void* hash = ...; // a pointer to the hash data
    int hashDataLength = ...;
    void** thumbnails = ...; // an array of pointers, each one points to thumbnail data
    int* thumbnailDataLengths = ...; // an array of ints, each one is the length of the thumbnail data with the same index

    jobject hashBuffer = env->NewDirectByteBuffer(hash, hashDataLength);
    env->SetObjectArrayElement(output, 0, hashBuffer);

    for (int i = 0; i < nThumbnails; i++)
        env->SetObjectArrayElement(output, i + 1, env->NewDirectByteBuffer(thumbnails[i], thumbnailDataLengths[i]));
}

編集:

入力に使用できるのはバイト配列だけです。バイト配列をバイトバッファーでラップしても、同じ税金がかかりませんか?また、配列の構文も http://developer.Android.com/training/articles/perf-jni.html#region_calls です。コピーはまだ可能ですが。

GetByteArrayRegionは常にバッファに書き込むため、毎回コピーを作成するため、代わりにGetByteArrayElementsをお勧めします。 Java側の直接ByteBufferに配列をコピーすることも、GetByteArrayElementsが配列を固定する場合に最終的に回避できるコピーがあるため、最善のアイデアではありません。

ネイティブデータをラップするバイトバッファーを作成する場合、それをクリーンアップする責任は誰にありますか? Javaはこれを解放するタイミングがわからないので、memcpyを実行しました。このメモリは、スタック、ヒープ、またはカスタムアロケータからである可能性があります。バグ。

データがスタックにある場合は、 must コピーしてJava配列、 Java=コードまたはヒープ上のどこかに作成された直接ByteBuffer(およびその場所を指す直接ByteBuffer)。ヒープ上にある場合、その直接ByteBufferを安全に使用できますNewDirectByteBufferを使用して作成されますが、誰もメモリを解放できないことが保証されます。ヒープメモリが解放されたら、ByteBufferオブジェクトを使用する必要はありません。Javaはネイティブを削除しようとしませんByteBufferを使用して作成された直接のNewDirectByteBufferがGCされた場合のメモリ。バッファも手動で作成したため、手動で処理する必要があります。

25
main--
  1. バイト配列

  2. 私は似たようなものにしなければならなかった、私はバイト配列のコンテナー(ベクターまたは何か)を返しました。他のプログラマの1人がこれをコールバックとして実装しました(これは簡単ですが、少しばかげています)。例えばJNIコードは、各応答に対してJava=メソッドを呼び出し、次に(JNIコードへの)元の呼び出しが返されます。これは問題なく動作します。

1
tallen