Каковы количественные накладные расходы на вызов JNI?

Основываясь только на производительности, примерно, сколько "простых" строк java является эквивалентным ударом производительности при вызове JNI?

Или попытаться выразить вопрос более конкретным образом, если простая java-операция, такая как

someIntVar1 = someIntVar2 + someIntVar3;

был присвоен индекс "CPU work" 1, каков будет типичный (ballpark) индекс "работа процессора" накладных расходов на вызов JNI?


Этот вопрос игнорирует время, ожидаемое для запуска собственного кода. В телефоническом языке речь идет только о части "падения флага" вызова, а не о "скорости вызова".


Причина, по которой задается этот вопрос, заключается в том, чтобы иметь "эмпирическое правило", чтобы знать, когда нужно пытаться кодировать вызов JNI, когда вы знаете собственную стоимость (от прямого тестирования) и стоимость Java для данной операции. Это может помочь вам быстро избежать проблем с кодированием вызова JNI только для того, чтобы найти, что служебная информация выноса потребляла любое преимущество использования собственного кода.

Edit:

Некоторые люди зацикливаются на вариациях в ЦП, ОЗУ и т.д. Все они практически не имеют отношения к вопросу - я прошу относительную стоимость строк Java-кода. Если ЦП и ОЗУ бедны, они плохо подходят как для java, так и для JNI, поэтому экологические соображения должны быть сбалансированы. Версия JVM также входит в категорию "неулокальных".

Этот вопрос не требует абсолютного времени в наносекундах, а скорее "рабочего усилия" парка шаров в единицах "строк простого кода Java".

Ответ 1

Быстрый тест профайлера дает:

Класс 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;
}

Результаты:

single invocation10 calls in a loop100 calls in a loop

Сведения о системе:

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

Обновление: Микроконтроллеры Caliper для x86 (32/64 бит) и ARMv6 выглядят следующим образом:

Класс 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/Hotspot/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/phenom 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 примерно эквивалентен 10-25 java-операциям на типичном (x86) аппаратном обеспечении и Hotspot VM. Неудивительно, что при значительно меньшей оптимизации Zero VM результаты совершенно разные (3-4 ops).


Благодарим вас за участие и советы для @Giovanni Azua и @Марко Топольник.

Ответ 2

Вы должны сами проверить, что такое "латентность". Задержка определяется в технике как время, необходимое для отправки сообщения нулевой длины. В этом контексте он будет соответствовать написанию самой маленькой Java-программы, которая вызывает do_nothing пустую С++-функцию и вычисляет среднее значение и stddev прошедшего времени более чем за 30 измерений (выполните пару дополнительных разминок). Вы можете быть удивлены, что разные средние результаты делают то же самое для разных версий и платформ JDK.

Только это даст вам окончательный ответ о том, имеет ли смысл использование JNI для вашей целевой среды.

Ответ 3

Итак, я просто протестировал "латентность" для вызова JNI на C в Windows 8.1, 64-разрядный, с использованием Eclipse Mars IDE, JDK 1.8.0_74 и профилировщика VirtualVM 1.3.8 с надстройкой "Запуск профиля".

Настройка: (два метода)
SOMETHING() передает аргументы, делает вещи и возвращает аргументы
NOTHING() передает те же аргументы, ничего не делает с ними и возвращает те же аргументы.

(каждый из них называется 270 раз)
Общее время выполнения для SOMETHING(): 6523 мс
Общее время выполнения для NOTHING(): 0.102мс

Таким образом, в моем случае вызовы JNI довольно незначительны.