ネイティブメソッドは高価ですか? JNI Secret Extension



Javaプログラマーがネイティブメソッドに頼るのはなぜですか? サードパーティのDLLライブラリを使用する場合があります。 他のケースでは、最適化されたCまたはアセンブラーコードにより重要なアルゴリズムを高速化します。 たとえば、ストリーミングメディアの処理、圧縮、暗号化など。



ただし、ネイティブメソッドの呼び出しは無料ではありません。 JNIのオーバーヘッドは、パフォーマンスの向上よりも大きい場合があります。 そしてすべてが含まれているからです:

  1. スタックフレームの作成。
  2. ABIに従って引数をシフトします。
  3. JNIハンドルでリンクをラップ( jobject



    );
  4. jclass



    JNIEnv*



    およびjclass



    追加の引数をjclass



    ます。
  5. synchronized



    メソッドの場合、モニターのキャプチャとリリース。
  6. ネイティブ関数の「遅延」リンク。
  7. メソッドの入り口と出口をトレースします。
  8. ストリームをin_Java



    状態からin_native



    、またはその逆に転送する
  9. セーフポイントを確認してください。
  10. 考えられる例外の処理。


しかし、多くの場合、ネイティブメソッドは単純です:例外をスローせず、ヒープに新しいオブジェクトを作成せず、スタックをバイパスせず、ハンドルを使用せず、同期されません。 彼らが不必要な行動をしないことは可能ですか?



はい。今日は、単純なJNIメソッドをより速く呼び出すためのHotSpot JVMのドキュメント化されていない機能について説明します。 この最適化はJava 7の最初のバージョンから登場しましたが、驚くべきことですが、誰もそれについて書いていません。



私たちが彼を知っているJNI



たとえば、 byte[]



配列を受け取り、要素の合計を返す単純なネイティブメソッドを考えます。 JNIで配列を操作するには、いくつかの方法があります。



クリティカルネイティブ



そして、ここが私たちの秘密のツールです。 外観上は、通常のJNIメソッドのように見えますが、 JavaCritical_



代わりにJava_



プレフィックスがJava_



ます。 引数のうち、 jclass



JNIEnv*



jclass



しており、 jbyteArray



代わりに2つの引数が渡されますjbyte* data



配列の長さとjbyte* data



配列の要素への生のポインタ。 したがって、Critical Nativeメソッドは、高価なJNI関数GetArrayLength



およびGetByteArrayElements



を呼び出す必要がありません-すぐに配列を操作できます。 このメソッドの期間中、GCは遅延します。



 JNIEXPORT jint JNICALL JavaCritical_bench_Natives_javaCriticalImpl(jint length, jbyte* buf) { return sum(buf, length); }
      
      





ご覧のとおり、実装に余分なものはありません。

ただし、メソッドがクリティカルネイティブになるには、次の厳しい制限を満たす必要があります。



Critical Nativesは、ネイティブに実装された暗号化関数の呼び出しを高速化するために、JDKのプライベートHotspot APIとして考案されました。 説明から見つけられる最大値は、バグトラッカーのタスクに関するコメントです 。 重要な機能: JavaCritical_



関数は、ホット(コンパイル済み)コードからのみ呼び出されるため、 JavaCritical_



実装に加えて、メソッドには「フォールバック」の従来のJNI実装も必要です。 ただし、他のJVMとの互換性のために、これはさらに優れています。



グラム単位はいくつですか?



さまざまな長さ(16、256、4KB、64KB、1MB)の配列の節約量を測定してみましょう。 当然JMHを使用します。

ベンチマーク
 @State(Scope.Benchmark) public class Natives { @Param({"16", "256", "4096", "65536", "1048576"}) int length; byte[] array; @Setup public void setup() { array = new byte[length]; } @GenerateMicroBenchmark public int arrayRegion() { return arrayRegionImpl(array); } @GenerateMicroBenchmark public int arrayElements() { return arrayElementsImpl(array); } @GenerateMicroBenchmark public int arrayElementsCritical() { return arrayElementsCriticalImpl(array); } @GenerateMicroBenchmark public int javaCritical() { return javaCriticalImpl(array); } static native int arrayRegionImpl(byte[] array); static native int arrayElementsImpl(byte[] array); static native int arrayElementsCriticalImpl(byte[] array); static native int javaCriticalImpl(byte[] array); static { System.loadLibrary("natives"); } }
      
      



結果
 Java(TM) SE Runtime Environment (build 1.7.0_51-b13) Java HotSpot(TM) 64-Bit Server VM (build 24.51-b03, mixed mode) Benchmark (length) Mode Samples Mean Mean error Units b.Natives.arrayElements 16 thrpt 5 7001,853 66,532 ops/ms b.Natives.arrayElements 256 thrpt 5 4151,384 89,509 ops/ms b.Natives.arrayElements 4096 thrpt 5 571,006 5,534 ops/ms b.Natives.arrayElements 65536 thrpt 5 37,745 2,814 ops/ms b.Natives.arrayElements 1048576 thrpt 5 1,462 0,017 ops/ms b.Natives.arrayElementsCritical 16 thrpt 5 14467,389 70,073 ops/ms b.Natives.arrayElementsCritical 256 thrpt 5 6088,534 218,885 ops/ms b.Natives.arrayElementsCritical 4096 thrpt 5 677,528 12,340 ops/ms b.Natives.arrayElementsCritical 65536 thrpt 5 44,484 0,914 ops/ms b.Natives.arrayElementsCritical 1048576 thrpt 5 2,788 0,020 ops/ms b.Natives.arrayRegion 16 thrpt 5 19057,185 268,072 ops/ms b.Natives.arrayRegion 256 thrpt 5 6722,180 46,057 ops/ms b.Natives.arrayRegion 4096 thrpt 5 612,198 5,555 ops/ms b.Natives.arrayRegion 65536 thrpt 5 37,488 0,981 ops/ms b.Natives.arrayRegion 1048576 thrpt 5 2,054 0,071 ops/ms b.Natives.javaCritical 16 thrpt 5 60779,676 234,483 ops/ms b.Natives.javaCritical 256 thrpt 5 9531,828 67,106 ops/ms b.Natives.javaCritical 4096 thrpt 5 707,566 13,330 ops/ms b.Natives.javaCritical 65536 thrpt 5 44,653 0,927 ops/ms b.Natives.javaCritical 1048576 thrpt 5 2,793 0,047 ops/ms
      
      







小さい配列の場合、JNI呼び出しのコストはメソッド自体の実行時間の何倍にもなります。 数百バイトの配列の場合、オーバーヘッドは有用な作業に匹敵します。 さて、マルチキロバイト配列の場合、呼び出す方法はそれほど重要ではありません-実際にはすべての時間が処理に費やされています。



結論



Critical Nativesは、JDK 7で導入されたHotSpotのプライベートJNI拡張です。特定のルールに従ってJNIのような関数を実装することにより、ネイティブメソッドを呼び出し、ネイティブコードでJava配列を処理するオーバーヘッドを大幅に削減できます。 ただし、Critical Nativeの実行中はGCを起動できないため、長時間にわたる機能の場合、このようなソリューションは機能しません。



All Articles