30

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

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) の背景にある理論的根拠 - 私がこれらのサムネイルを使って行う唯一のことは、それらをアップロードすることだとすると、

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

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

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

4

2 に答える 2

32

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

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

あなたが言ったように、(i) メソッドが返すデータの量がわからないため、機能しません。(ii) そのカスタム パッケージング プロトコルのため、複雑すぎます。(iii) の修正版を使用します。そのオブジェクトは必要ありません。ByteBuffer最初の要素がハッシュで、他の要素がサムネイルである s の配列を返すだけで済みます。そして、すべてのsを捨てるmemcpyことができます! これが direct の要点です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

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

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

于 2013-07-17T21:00:20.663 に答える
1
  1. バイト配列

  2. 私は似たようなことをしなければなりませんでした.Byte配列のコンテナ(Vectorか何か)を返しました。他のプログラマーの 1 人は、これをコールバックとして実装しました (これは簡単ですが、少しばかげていると思います)。たとえば、JNI コードは応答ごとに Java メソッドを呼び出し、その後 (JNI コードへの) 元の呼び出しが返されます。これでも問題なく動作します。

于 2013-07-17T21:01:33.870 に答える