63

パフォーマンスのみに基づくと、Java の「単純な」行数は、JNI 呼び出しを行う場合と同等のパフォーマンス ヒットになりますか?

または、次のような単純な Java 操作の場合、より具体的な方法で質問を表現しようとします。

someIntVar1 = someIntVar2 + someIntVar3;

の「CPU 作業」インデックスが与えられた場合1、JNI 呼び出しを行うオーバーヘッドの典型的な (大まかな) 「CPU 作業」インデックスは何でしょうか?


この質問では、ネイティブ コードの実行を待機する時間を無視しています。電話用語では、厳密には「コール レート」ではなく、コールの「フラグ フォール」部分に関するものです。


この質問をする理由は、特定の操作のネイティブ コスト (直接テストから) と Java コストがわかっている場合に、JNI 呼び出しのコーディングを試みる必要があるかどうかを知るための "経験則" を持つためです。コールアウトのオーバーヘッドがネイティブ コードを使用する利点を消費していることを確認するためだけに、JNI 呼び出しをコーディングする手間をすばやく回避するのに役立ちます。

編集:

一部の人々は、CPU、RAM などのバリエーションに夢中になっています。これらはすべて、質問とは実質的に無関係です。Java コードの行に対する相対的なコストを求めています。CPU と RAM が貧弱な場合、Java と JNI の両方に貧弱なので、環境への配慮はバランスを取る必要があります。JVM のバージョンも「無関係」のカテゴリに分類されます。

この質問は、ナノ秒単位の絶対的なタイミングを求めているのではなく、「単純な Java コードの行」単位での球場の「労力」を求めています。

4

3 に答える 3

49

クイック プロファイラーのテスト結果:

Java クラス:

public class Main {
    private static native int zero();

    private static int testNative() {
        return Main.zero();
    }

    private static int test() {
        return 0;
    }

    public static void main(String[] args) {
        testNative();
        test();
    }

    static {
         System.loadLibrary("foo");
    }
}

C ライブラリ:

#include <jni.h>
#include "Main.h"

JNIEXPORT int JNICALL 
Java_Main_zero(JNIEnv *env, jobject obj)
{
    return 0;
}

結果:

単一の呼び出し ループで 10 回の呼び出し ループで 100 回の呼び出し

システムの詳細:

java version "1.7.0_09"
OpenJDK Runtime Environment (IcedTea7 2.3.3) (7u9-2.3.3-1)
OpenJDK Server VM (build 23.2-b09, mixed mode)
Linux visor 3.2.0-4-686-pae #1 SMP Debian 3.2.32-1 i686 GNU/Linux

更新: x86 (32/64 ビット) およびARMv6の Caliper マイクロベンチマークは次のとおりです。

Java クラス:

public class Main extends SimpleBenchmark {
    private static native int zero();
    private Random random;
    private int[] primes;

    public int timeJniCall(int reps) {
        int r = 0;
        for (int i = 0; i < reps; i++) r += Main.zero();
        return r;
    }

    public int timeAddIntOperation(int reps) {
        int p = primes[random.nextInt(1) + 54];   // >= 257
        for (int i = 0; i < reps; i++) p += i;
        return p;
    }

    public long timeAddLongOperation(int reps) {
        long p = primes[random.nextInt(3) + 54];  // >= 257
        long inc = primes[random.nextInt(3) + 4]; // >= 11
        for (int i = 0; i < reps; i++) p += inc;
        return p;
    }

    @Override
    protected void setUp() throws Exception {
        random = new Random();
        primes = getPrimes(1000);
    }

    public static void main(String[] args) {
        Runner.main(Main.class, args);        
    }

    public static int[] getPrimes(int limit) {
        // returns array of primes under $limit, off-topic here
    }

    static {
        System.loadLibrary("foo");
    }
}

結果 (x86/i7500/ホットスポット/Linux):

Scenario{benchmark=JniCall} 11.34 ns; σ=0.02 ns @ 3 trials
Scenario{benchmark=AddIntOperation} 0.47 ns; σ=0.02 ns @ 10 trials
Scenario{benchmark=AddLongOperation} 0.92 ns; σ=0.02 ns @ 10 trials

       benchmark     ns linear runtime
         JniCall 11.335 ==============================
 AddIntOperation  0.466 =
AddLongOperation  0.921 ==

結果 (amd64/天才 960T/Hostspot/Linux):

Scenario{benchmark=JniCall} 6.66 ns; σ=0.22 ns @ 10 trials
Scenario{benchmark=AddIntOperation} 0.29 ns; σ=0.00 ns @ 3 trials
Scenario{benchmark=AddLongOperation} 0.26 ns; σ=0.00 ns @ 3 trials

   benchmark    ns linear runtime
         JniCall 6.657 ==============================
 AddIntOperation 0.291 =
AddLongOperation 0.259 =

結果 (armv6/BCM2708/Zero/Linux):

Scenario{benchmark=JniCall} 678.59 ns; σ=1.44 ns @ 3 trials
Scenario{benchmark=AddIntOperation} 183.46 ns; σ=0.54 ns @ 3 trials
Scenario{benchmark=AddLongOperation} 199.36 ns; σ=0.65 ns @ 3 trials

   benchmark  ns linear runtime
         JniCall 679 ==============================
 AddIntOperation 183 ========
AddLongOperation 199 ========

少し要約すると、JNI呼び出しは、一般的な ( x86 ) ハードウェアおよびHotspot VMでの 10 ~ 25 の Java ops にほぼ相当するようです。当然のことながら、あまり最適化されていないZero VMでは、結果はまったく異なります (3 ~ 4 オペレーション)。


@ Giovanni Azuaと @ Marko Topolnikの参加とヒントに感謝します。

于 2012-12-20T18:02:29.040 に答える
6

そこで、Eclipse Mars IDE、JDK 1.8.0_74、VirtualVM プロファイラー 1.3.8 と Profile Startup アドオンを使用して、Windows 8.1、64 ビットで C への JNI 呼び出しの「レイテンシー」をテストしました。

セットアップ: (2 つのメソッド)
SOMETHING() は引数を渡し、処理を行い、引数を返します
NOTHING() は同じ引数を渡し、何もせず、同じ引数を返します。

(それぞれ 270 回呼び出されます)
SOMETHING() の合計実行時間: 6523ms
NOTHING() の合計実行時間: 0.102ms

したがって、私の場合、JNI 呼び出しはごくわずかです。

于 2016-03-21T20:42:20.113 に答える
2

「レイテンシ」が何であるかを実際に自分でテストする必要があります。待ち時間は、エンジニアリングでは、長さ 0 のメッセージを送信するのにかかる時間として定義されています。このコンテキストでは、do_nothing空の C++ 関数を呼び出し、30 回の測定の経過時間の平均と標準偏差を計算する最小の Java プログラムを作成することに相当します (追加のウォームアップ呼び出しを 2 回実行します)。さまざまな JDK バージョンとプラットフォームで同じことを行っているさまざまな平均結果に驚くかもしれません。

そうするだけで、JNI を使用することがターゲット環境にとって意味があるかどうかの最終的な答えが得られます。

于 2012-12-20T13:25:07.790 に答える