Hlavní navigace

Pohled pod kapotu JVM – přednosti a zápory využití JNI při optimalizacích (3)

Pavel Tišnovský 19. 11. 2013

V dnešní části seriálu o jazyce Java se podruhé budeme zabývat problematikou polí vytvořených v javovské části aplikace a předávaných do nativní (většinou céčkové) funkce. Na dvojici benchmarků si ukážeme, jaké JNI funkce je vhodné využít a které funkce jsou naopak z časového a paměťového hlediska nevýhodné.

Obsah

1. Pohled pod kapotu JVM – přednosti a zápory využití JNI při optimalizacích (3)

2. Benchmark měřící rychlost výpočtu průměrné hodnoty všech prvků v poli

3. Překlad a spuštění benchmarku

4. Výsledky prvního benchmarku

5. Strojový kód generovaný JIT překladačem

6. Strojový kód generovaný překladačem GCC

7. Částečné řešení problému při sdílení polí – využití regionů

8. Benchmark pracující pouze s částí sdíleného pole

9. Překlad a spuštění benchmarku

10. Výsledky benchmarku

11. Repositář se zdrojovými soubory i se skripty pro překlad a spuštění

12. Odkazy na Internetu

1. Pohled pod kapotu JVM – přednosti a zápory využití JNI při optimalizacích (3)

V předchozí části seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme si vysvětlili, jaké problémy na vývojáře čekají ve chvíli, kdy potřebují sdílet pole (jehož prvky jsou většinou primitivního datového typu) mezi javovskou částí aplikace a částí nativní, s níž se komunikuje přes rozhraní JNI (Java Native Interface). Víme již, že většina současných virtuálních strojů Javy při předání pole do nativní funkce a při přístupu k tomuto poli provede kopii všech prvků pole, aby se zajistila práce správce paměti (garbage collector). Ten může být spuštěn zcela nezávisle na nativní funkci (která může trvat libovolně dlouho) a teprve při ukončení práce s polem je možné provést zpětnou kopii prvků a tím i synchronizaci dat. Tato technologie je zajištěna několika funkcemi JNI vypsanými v následující tabulce:

# Návratový typ Funkce Popis
1 jboolean * GetBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu boolean[]
2 jbyte * GetByteArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu byte[]
3 jchar * GetCharArrayElements(JNIEnv *env, jcharArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu char[]
4 jshort * GetShortArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu short[]
5 jint * GetIntArrayElements(JNIEnv *env, jintArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu int[]
6 jlong * GetLongArrayElements(JNIEnv *env, jlongArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu long[]
7 jfloat * GetFloatArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu float[]
8 jdouble * GetDoubleArrayElements(JNIEnv *env, jdoubleArray array, jboolean *isCopy) vrátí ukazatel na první prvek v poli typu double[]

Uvolnění pole a případnou zpětnou kopii zajišťují funkce:

# Návratový typ Funkce Popis
1 void ReleaseBooleanArrayElements(JNIEnv *env, jbooleanArray array, jboolean *elems, jint mode) uvolnění pole získaného funkcí GetBooleanArrayElements
2 void ReleaseByteArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) uvolnění pole získaného funkcí GetByteArrayElements
3 void ReleaseCharArrayElements(JNIEnv *env, jcharArray array, jchar *elems, jint mode) uvolnění pole získaného funkcí GetCharArrayElements
4 void ReleaseShortArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) uvolnění pole získaného funkcí GetShortArrayElements
5 void ReleaseIntArrayElements(JNIEnv *env, jintArray array, jint *elems, jint mode) uvolnění pole získaného funkcí GetIntArrayElements
6 void ReleaseLongArrayElements(JNIEnv *env, jlongArray array, jlong *elems, jint mode) uvolnění pole získaného funkcí GetLongArrayElements
7 void ReleaseFloatArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) uvolnění pole získaného funkcí GetFloatArrayElements
8 void ReleaseDoubleArrayElements(JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode) uvolnění pole získaného funkcí GetDoubleArrayElements

Vzhledem k tomu, že kopie celého pole může být nevýhodná jak kvůli vyšší časové složitosti, tak i paměťovým nárokům, byla do rozhraní JNI přidána další dvojice funkcí, které se (opět na většině implementací) snaží neprovádět kopii pole, protože se počítá s tím, že mezi oběma funkcemi bude pouze krátký kód, který navíc nesmí volat další funkce JNI:

# Návratový typ Funkce
1 void * GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboolean *isCopy)
2 void ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, void *carray, jint mode)

2. Benchmark měřící rychlost výpočtu průměrné hodnoty všech prvků v poli

Jakým způsobem se projeví kopie (popř. i zpětná kopie) polí v nativních funkcích si ukážeme na benchmarku. Tento benchmark provádí několikrát stejnou operaci – výpočet průměru všech prvků pole typu float[] – a to čtyřmi různými způsoby. První výpočet je založen na nativní metodě používající JNI funkce GetFloatArrayElements()ReleaseFloatArrayElements() se zpětnou kopií pole (což je zbytečné). Druhý výpočet používá tytéž funkce, ovšem již neprovádí zpětnou kopii. Třetí nativní funkce využívá volání GetPrimitiveArrayCritical()ReleasePrimitiveArrayCritical() a čtvrtý výpočet průměru je implementován přímo v Javě:

JNITest6.java:

/**
 * Jednoduchy benchmark pro porovnani rychlosti pristupu k polim
 * v nativnich metodach/funkcich.
 *
 * @author Pavel Tisnovsky
 */
public class JNITest6 {
 
    /**
     * Pocet opakovani zahrivaci faze benchmarku.
     */
    private static final int WARMUP_ITERS = 15000;
 
    /**
     * Pocet opakovani merene faze benchmarku.
     */
    private static final int BENCHMARK_ITERS = 15000;
 
    /**
     * Velikost pole, ktere bude pouzito v benchmarku.
     */
    private static final int ARRAY_SIZE = 200000;
 
    /**
     * Testovaci pole.
     */
    static float[] array = new float[ARRAY_SIZE];
 
    /**
     * Naplneni pole daty.
     */
    static {
        for (int i=0; i<ARRAY_SIZE; i++) {
            array[i] = (float)(i+1);
        }
    }
 
    /**
     * Nativni metody testovane benchmarkem.
     */
    native public static float averageN1(float[] array);
    native public static float averageN2(float[] array);
    native public static float averageN3(float[] array);
 
    /**
     * Obdobna metoda napsana v Jave.
     */
    public static float averageJ(float[] array) {
        float average = 0;
        // ziskat delku pole
        int length = array.length;
 
        // pruchod polem a vypocet sumy prvku
        for (int i=0; i<length; i++) {
            average += array[i];
        }
 
        // vypocet prumeru
        return average/length;
    }
 
    /**
     * Spusteni benchmarku.
     */
    private static void runJNIBenchmarks() {
        warmup();
        benchmark();
    }
 
    /**
     * Vypis vypocteneho vysledku (jen pro kontrolu).
     */
    private static void printResult(float result) {
        System.out.print("    result=");
        System.out.println(result);
    }
 
    /**
     * Zahrivaci faze benchmarku.
     */
    private static void warmup() {
        System.out.println("Warmup phase...");
        float result;
 
        result = 0;
        // donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = averageJ(array);
        }
        printResult(result);
 
        result = 0;
        // taktez zde donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = averageN1(array);
        }
        printResult(result);
 
        result = 0;
        // taktez zde donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = averageN2(array);
        }
        printResult(result);
 
        result = 0;
        // taktez zde donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = averageN3(array);
        }
        printResult(result);
 
        System.out.println("done");
    }
 
    /**
     * Vlastni mereny benchmark.
     */
    private static void benchmark() {
        System.out.println("Benchmark phase...");
        long t1, t2, delta_t;
        float result;
 
        // provest test a zmerit cas behu prvniho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = averageJ(array);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro prvni test
        System.out.format("JITted method time:      %,12d ns\n", delta_t);
 
        // provest test a zmerit cas behu druheho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = averageN1(array);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro druhy test
        System.out.format("native function #1 time: %,12d ns\n", delta_t);
 
        // provest test a zmerit cas behu tretiho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = averageN2(array);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro treti test
        System.out.format("native function #2 time: %,12d ns\n", delta_t);
 
        // provest test a zmerit cas behu ctvrteho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = averageN3(array);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro ctvrty test
        System.out.format("native function #3 time: %,12d ns\n", delta_t);
 
        System.out.println("done");
    }
 
    /**
     * Spusteni benchmarku.
     */
    public static void main(String[] args) {
        System.loadLibrary("JNITest6");
        runJNIBenchmarks();
    }
}

JNITest6.h (generovaný přes nástroj javah):

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest6 */
 
#ifndef _Included_JNITest6
#define _Included_JNITest6
#ifdef __cplusplus
extern "C" {
#endif
#undef JNITest6_WARMUP_ITERS
#define JNITest6_WARMUP_ITERS 15000L
#undef JNITest6_BENCHMARK_ITERS
#define JNITest6_BENCHMARK_ITERS 15000L
#undef JNITest6_ARRAY_SIZE
#define JNITest6_ARRAY_SIZE 100000L
/*
 * Class:     JNITest6
 * Method:    averageN1
 * Signature: ([F)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN1
  (JNIEnv *, jclass, jfloatArray);
 
/*
 * Class:     JNITest6
 * Method:    averageN2
 * Signature: ([F)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN2
  (JNIEnv *, jclass, jfloatArray);
 
/*
 * Class:     JNITest6
 * Method:    averageN2
 * Signature: ([F)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN3
  (JNIEnv *, jclass, jfloatArray);
 
#ifdef __cplusplus
}
#endif
#endif

JNITest6.c (nativní část aplikace):

#include "JNITest6.h"
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN1(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
    jint release_mode = 0;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
    {
        average += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
 
    /* vypocet prumeru */
    return average/length;
}
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN2(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
    {
        average += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
 
    /* vypocet prumeru */
    return average/length;
}
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN3(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
    {
        average += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleasePrimitiveArrayCritical(jni_env, (jarray)array, (void*)f, release_mode);
 
    /* vypocet prumeru */
    return average/length;
}

3. Překlad a spuštění benchmarku

Překlad javovské části benchmarku je velmi snadný a není rozdílný od překladu všech pěti předchozích příkladů, které jsme si v rámci popisu rozhraní JNI již vyzkoušeli:

javac JNITest6.java

Následně, tj. ve chvíli, kdy již existuje soubor JNITest6.class obsahující přeloženou třídu JNITest6, je nutné vygenerovat hlavičkový soubor JNITest6.h:

javah JNITest6

Překlad nativní části benchmarku se provede obdobným způsobem, jako tomu bylo u obou předchozích demonstračních příkladů uvedených minule (včetně voleb pro optimalizaci):

gcc -O3 -funroll-all-loops -shared \
                 -I/usr/lib/jvm/java-1.7.0-openjdk/include/ \
                 -o libJNITest6.so JNITest6.c

Popř. na platformách vyžadujících PIC (position independent code) v knihovnách:

gcc -O3 -funroll-all-loops -shared -fPIC \
                  -I/usr/lib/jvm/java-1.7.0-openjdk/include/ \
                  -I/usr/lib/jvm/java-1.7.0-openjdk/include/linux \
                  -o libJNITest6.so JNITest6.c

Na systému Windows s nainstalovanou OpenJDK 7 či Oracle JDK 7 by mohl překlad s využitím mingw vypadat následovně:

gcc -O3 -funroll-all-loops -shared -I"c:\Program Files\Java\jdk1.7.0_25\include" -o JNITest4.dll JNITest4.c

Povšimněte si, že při překladu byly povoleny optimalizace, aby se výsledný nativní strojový kód co nejvíce přiblížil kódu vygenerovanému JIT překladačem.

Aby bylo měření spravedlivé, je nutné při spuštění benchmarku povolit JIT překlad:

export LD_LIBRARY_PATH=.
java -Xcomp JNITest6

4. Výsledky prvního benchmarku

Po spuštění benchmarku se na standardní výstup vypíšou informace o běhu i dosažené časy výpočtu. Na 64bitovém systému s procesorem Intel i7 byly získány následující hodnoty:

Warmup phase...
    result=100001.3
    result=100001.3
    result=100001.3
    result=100001.3
done
Benchmark phase...
    result=100001.3
JITted method time:      2,533,364,426 ns
    result=100001.3
native function #1 time: 6,630,523,834 ns
    result=100001.3
native function #2 time: 5,727,310,449 ns
    result=100001.3
native function #3 time: 2,511,554,841 ns
done

Tyto výsledky jsou velmi zajímavé a možná i poněkud neočekávané. Ukazuje se totiž, že javovská varianta není (resp. v obecném případě nemusí být) nejpomalejší, ale naopak soupeří s nejrychlejší céčkovou variantou. Nejpomalejší je nativní funkce používající volání GetFloatArrayElements()ReleaseFloatArrayElements() se zpětnou kopií, nejrychlejší je nativní funkce využívající GetPrimitiveArrayCritical()ReleasePrimitiveArrayCritical(), z čehož je možné usuzovat, že JVM neprovedlo defenzivní kopii pole. Zde se tedy ukazuje, že se při použití nativních funkcí volaných přes rozhraní JNI vyplatí sledovat profiler a popř. se snažit zamezit zbytečné kopii polí, nebo (pokud je to možné) využít kopii jen části pole, což je problematika, kterou se budeme podrobněji zabývat v navazujících kapitolách.

5. Strojový kód generovaný JIT překladačem

Zajímavé bude porovnání strojového kódu generovaného JIT překladačem (typu server) se strojovým kódem vytvořeným céčkovým překladačem. Nejprve si uveďme, jak bude vypadat výstup JITu v případě, že byl benchmark spuštěn na 64bitovém procesoru s architekturou x86_64 s podporou rozšíření instrukčních sad MMX, SSE i SSE2. Zajímá nás samozřejmě pouze tvar metody JNITest6.averageJ(). Výstup JITu získáme následujícím způsobem:

java -server -XX:CompileThreshold=10000 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly JNITest6
Decoding compiled method 0x009b8188:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} 'averageJ' '([F)F' in 'JNITest6'
  0x009b8280: int3
  0x009b8281: xchg   %ax,%ax
  0x009b8284: mov    %eax,0xffffc000(%esp)
  0x009b828b: push   %ebp
  0x009b828c: sub    $0x18,%esp
  0x009b828f: mov    (%ecx),%ebx
  0x009b8291: mov    0xc(%ecx),%esi
  0x009b8294: movss  0x8(%ecx),%xmm0
  0x009b8299: movss  %xmm0,0x8(%esp)
  0x009b829f: mov    0x4(%ecx),%ebp
  0x009b82a2: mov    %ecx,(%esp)
  0x009b82a5: call   0x6ee57140         ;   {runtime_call}
  0x009b82aa: test   %esi,%esi
  0x009b82ac: je     0x009b839e
  0x009b82b2: mov    0x4(%esi),%ecx
  0x009b82b5: cmp    $0xbb30510,%ecx    ;   {oop({type array float})}
  0x009b82bb: jne    0x009b83bd         ;*iload_3
                                        ; - JNITest6::averageJ@7 (line 41)
  0x009b82c1: cmp    %ebp,%ebx
  0x009b82c3: jge    0x009b83d1         ;*if_icmpge
                                        ; - JNITest6::averageJ@9 (line 41)
  0x009b82c9: mov    0x8(%esi),%ecx     ;*faload
                                        ; - JNITest6::averageJ@15 (line 42)
                                        ; implicit exception: dispatches to 0x009b83a8
  0x009b82cc: cmp    %ecx,%ebx
  0x009b82ce: jae    0x009b83a8
  0x009b82d4: mov    %ebp,%edi
  0x009b82d6: dec    %edi
  0x009b82d7: cmp    %ecx,%edi
  0x009b82d9: jae    0x009b83a8
  0x009b82df: mov    %ebx,%edi
  0x009b82e1: inc    %edi               ;*fload_1
                                        ; - JNITest6::averageJ@12 (line 42)
  0x009b82e2: movss  0x8(%esp),%xmm1
  0x009b82e8: addss  0xc(%esi,%ebx,4),%xmm1  ;*fadd
                                        ; - JNITest6::averageJ@16 (line 42)
  0x009b82ee: inc    %ebx               ;*iinc
                                        ; - JNITest6::averageJ@18 (line 41)
  0x009b82ef: cmp    %edi,%ebx
  0x009b82f1: jge    0x009b82fb         ;*if_icmpge
                                        ; - JNITest6::averageJ@9 (line 41)
  0x009b82f3: movss  %xmm1,0x8(%esp)
  0x009b82f9: jmp    0x009b82e2
  0x009b82fb: mov    %ebp,%edi
  0x009b82fd: add    $0xfffffff1,%edi
  0x009b8300: mov    $0x80000000,%eax
  0x009b8305: cmp    %edi,%ebp
  0x009b8307: cmovl  %eax,%edi
  0x009b830a: cmp    %edi,%ebx
  0x009b830c: jge    0x009b8377
  0x009b830e: xchg   %ax,%ax            ;*fload_1
                                        ; - JNITest6::averageJ@12 (line 42)
  0x009b8310: addss  0xc(%esi,%ebx,4),%xmm1
  0x009b8316: addss  0x10(%esi,%ebx,4),%xmm1
  0x009b831c: addss  0x14(%esi,%ebx,4),%xmm1
  0x009b8322: addss  0x18(%esi,%ebx,4),%xmm1
  0x009b8328: addss  0x1c(%esi,%ebx,4),%xmm1
  0x009b832e: addss  0x20(%esi,%ebx,4),%xmm1
  0x009b8334: addss  0x24(%esi,%ebx,4),%xmm1
  0x009b833a: addss  0x28(%esi,%ebx,4),%xmm1
  0x009b8340: addss  0x2c(%esi,%ebx,4),%xmm1
  0x009b8346: addss  0x30(%esi,%ebx,4),%xmm1
  0x009b834c: addss  0x34(%esi,%ebx,4),%xmm1
  0x009b8352: addss  0x38(%esi,%ebx,4),%xmm1
  0x009b8358: addss  0x3c(%esi,%ebx,4),%xmm1
  0x009b835e: addss  0x40(%esi,%ebx,4),%xmm1
  0x009b8364: addss  0x44(%esi,%ebx,4),%xmm1
  0x009b836a: addss  0x48(%esi,%ebx,4),%xmm1  ;*fadd
                                        ; - JNITest6::averageJ@16 (line 42)
  0x009b8370: add    $0x10,%ebx         ;*iinc
                                        ; - JNITest6::averageJ@18 (line 41)
  0x009b8373: cmp    %edi,%ebx
  0x009b8375: jl     0x009b8310         ;*if_icmpge
                                        ; - JNITest6::averageJ@9 (line 41)
  0x009b8377: cmp    %ebp,%ebx
  0x009b8379: jge    0x009b8387
  0x009b837b: nop                       ;*fload_1
                                        ; - JNITest6::averageJ@12 (line 42)
  0x009b837c: addss  0xc(%esi,%ebx,4),%xmm1  ;*fadd
                                        ; - JNITest6::averageJ@16 (line 42)
  0x009b8382: inc    %ebx               ;*iinc
                                        ; - JNITest6::averageJ@18 (line 41)
  0x009b8383: cmp    %ebp,%ebx
  0x009b8385: jl     0x009b837c         ;*iload_3
                                        ; - JNITest6::averageJ@7 (line 41)
  0x009b8387: cvtsi2ss %ebp,%xmm0
  0x009b838b: divss  %xmm0,%xmm1        ;*fdiv
                                        ; - JNITest6::averageJ@27 (line 46)
  0x009b838f: movss  %xmm1,%xmm0
  0x009b8393: add    $0x18,%esp
  0x009b8396: pop    %ebp
  0x009b8397: test   %eax,0x940000      ;   {poll_return}
  0x009b839d: ret
  0x009b839e: mov    $0x0,%esi
  0x009b83a3: jmp    0x009b82c1
  0x009b83a8: mov    $0xffffff86,%ecx
  0x009b83ad: mov    %esi,0x4(%esp)
  0x009b83b1: mov    %ebx,0xc(%esp)
  0x009b83b5: xchg   %ax,%ax
  0x009b83b7: call   0x0099dd00         ; OopMap{[4]=Oop off=316}
                                        ;*fload_1
                                        ; - JNITest6::averageJ@12 (line 42)
                                        ;   {runtime_call}
  0x009b83bc: int3
  0x009b83bd: mov    $0xffffffad,%ecx
  0x009b83c2: mov    %esi,0x4(%esp)
  0x009b83c6: mov    %ebx,0xc(%esp)
  0x009b83ca: nop
  0x009b83cb: call   0x0099dd00         ; OopMap{[4]=Oop off=336}
                                        ;*iload_3
                                        ; - JNITest6::averageJ@7 (line 41)
                                        ;   {runtime_call}
  0x009b83d0: int3                      ;*iload_3
                                        ; - JNITest6::averageJ@7 (line 41)
  0x009b83d1: movss  0x8(%esp),%xmm1
  0x009b83d7: jmp    0x009b8387
  0x009b83d9: hlt
  0x009b83da: hlt
  0x009b83db: hlt
  0x009b83dc: hlt
  0x009b83dd: hlt
  0x009b83de: hlt
  0x009b83df: hlt
[Exception Handler]
[Stub Code]
  0x009b83e0: jmp    0x009b7240         ;   {no_reloc}
[Deopt Handler Code]
  0x009b83e5: push   $0x9b83e5          ;   {section_word}
  0x009b83ea: jmp    0x0099e280         ;   {runtime_call}
  0x009b83ef: hlt

Nejdůležitější je tělo smyčky, v níž se prochází polem a počítá se suma všech prvků tohoto pole. JIT překladač typu server se v tomto případě pokusil o rozbalení smyčky, i když poněkud naivním způsobem (hodnoty před závorkou jsou offsety):

                                        ; - JNITest6::averageJ@12 (line 42)
  0x009b8310: addss  0xc(%esi,%ebx,4),%xmm1
  0x009b8316: addss  0x10(%esi,%ebx,4),%xmm1
  0x009b831c: addss  0x14(%esi,%ebx,4),%xmm1
  0x009b8322: addss  0x18(%esi,%ebx,4),%xmm1
  0x009b8328: addss  0x1c(%esi,%ebx,4),%xmm1
  0x009b832e: addss  0x20(%esi,%ebx,4),%xmm1
  0x009b8334: addss  0x24(%esi,%ebx,4),%xmm1
  0x009b833a: addss  0x28(%esi,%ebx,4),%xmm1
  0x009b8340: addss  0x2c(%esi,%ebx,4),%xmm1
  0x009b8346: addss  0x30(%esi,%ebx,4),%xmm1
  0x009b834c: addss  0x34(%esi,%ebx,4),%xmm1
  0x009b8352: addss  0x38(%esi,%ebx,4),%xmm1
  0x009b8358: addss  0x3c(%esi,%ebx,4),%xmm1
  0x009b835e: addss  0x40(%esi,%ebx,4),%xmm1
  0x009b8364: addss  0x44(%esi,%ebx,4),%xmm1
  0x009b836a: addss  0x48(%esi,%ebx,4),%xmm1  ;*fadd
                                        ; - JNITest6::averageJ@16 (line 42)
  0x009b8370: add    $0x10,%ebx         ;*iinc

6. Strojový kód generovaný překladačem GCC

Nyní si ukažme, jak vypadají nativní metody přeložené překladačem GCC při použití voleb -O3 a -funroll-all-loops. Překlad do assembleru lze provést dvěma způsoby: buď volbou -S (základní možnost), popř. (podrobnější výpis) použitím -Wa,–ahl (-Wa posílá následující přepínače do assembleru):

gcc -Wall -ansi -O3 -funroll-all-loops \
    -g -Wa,-ahl=out.asm \
    -I/usr/lib/jvm/java-1.7.0-openjdk/include/ \
    -I/usr/lib/jvm/java-1.7.0-openjdk/include/linux \
    -o libJNITest6.so JNITest6.c

Osobně ale preferuji jiný způsob získání čitelného kódu v assembleru. Nejprve se provede překlad společně s vygenerováním informací o symbolech používaných debuggery (-g). Následně se objektový kód zpracuje utilitou objdump s parametry -d, -S a popř. i -M intel (to pro ty z nás, kteří zrovna nemilují AT&T syntaxi :-p):

gcc -Wall -ansi -O3 -funroll-all-loops \
    -g -c \
    -I/usr/lib/jvm/java-1.7.0-openjdk/include/ \
    -I/usr/lib/jvm/java-1.7.0-openjdk/include/linux \
    JNITest6.c
 
objdump -d -M intel -S JNITest6.o > out2.asm

Výsledek si můžete prohlédnout pod tímto odstavcem:

JNITest6.o:     file format elf64-x86-64
 
 
Disassembly of section .text:
 
0000000000000000 <Java_JNITest6_averageN1>:
#include "JNITest6.h"
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN1(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
   0:   41 54                   push   r12
    jint release_mode = 0;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
   2:   48 89 d6                mov    rsi,rdx
#include "JNITest6.h"
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN1(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
   5:   49 89 d4                mov    r12,rdx
   8:   55                      push   rbp
   9:   53                      push   rbx
   a:   48 89 fb                mov    rbx,rdi
   d:   48 83 ec 10             sub    rsp,0x10
    jint release_mode = 0;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
  11:   48 8b 07                mov    rax,QWORD PTR [rdi]
  14:   ff 90 58 05 00 00       call   QWORD PTR [rax+0x558]
  1a:   89 c5                   mov    ebp,eax
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
  1c:   48 8b 03                mov    rax,QWORD PTR [rbx]
  1f:   31 d2                   xor    edx,edx
  21:   4c 89 e6                mov    rsi,r12
  24:   48 89 df                mov    rdi,rbx
  27:   ff 90 e8 05 00 00       call   QWORD PTR [rax+0x5e8]
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
  2d:   85 ed                   test   ebp,ebp
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
  2f:   48 89 c2                mov    rdx,rax
#include "JNITest6.h"
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN1(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
    jint release_mode = 0;
    jfloat average = 0;
  32:   0f 57 c0                xorps  xmm0,xmm0
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
  35:   0f 8e be 00 00 00       jle    f9 <Java_JNITest6_averageN1+0xf9>
  3b:   44 8d 4d ff             lea    r9d,[rbp-0x1]
    {
        average += *item;
  3f:   f3 0f 58 00             addss  xmm0,DWORD PTR [rax]
  43:   b9 01 00 00 00          mov    ecx,0x1
  48:   41 83 e1 07             and    r9d,0x7
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
  4c:   83 fd 01                cmp    ebp,0x1
  4f:   0f 8e a4 00 00 00       jle    f9 <Java_JNITest6_averageN1+0xf9>
  55:   45 85 c9                test   r9d,r9d
  58:   74 68                   je     c2 <Java_JNITest6_averageN1+0xc2>
  5a:   41 83 f9 01             cmp    r9d,0x1
  5e:   74 55                   je     b5 <Java_JNITest6_averageN1+0xb5>
  60:   41 83 f9 02             cmp    r9d,0x2
  64:   74 46                   je     ac <Java_JNITest6_averageN1+0xac>
  66:   41 83 f9 03             cmp    r9d,0x3
  6a:   74 37                   je     a3 <Java_JNITest6_averageN1+0xa3>
  6c:   41 83 f9 04             cmp    r9d,0x4
  70:   74 28                   je     9a <Java_JNITest6_averageN1+0x9a>
  72:   41 83 f9 05             cmp    r9d,0x5
  76:   74 19                   je     91 <Java_JNITest6_averageN1+0x91>
  78:   41 83 f9 06             cmp    r9d,0x6
  7c:   74 0a                   je     88 <Java_JNITest6_averageN1+0x88>
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
  be:   39 cd                   cmp    ebp,ecx
  c0:   7e 37                   jle    f9 <Java_JNITest6_averageN1+0xf9>
    {
        average += *item;
  c2:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
  c7:   f3 0f 58 44 8a 04       addss  xmm0,DWORD PTR [rdx+rcx*4+0x4]
  cd:   f3 0f 58 44 8a 08       addss  xmm0,DWORD PTR [rdx+rcx*4+0x8]
  d3:   f3 0f 58 44 8a 0c       addss  xmm0,DWORD PTR [rdx+rcx*4+0xc]
  d9:   f3 0f 58 44 8a 10       addss  xmm0,DWORD PTR [rdx+rcx*4+0x10]
  df:   f3 0f 58 44 8a 14       addss  xmm0,DWORD PTR [rdx+rcx*4+0x14]
  e5:   f3 0f 58 44 8a 18       addss  xmm0,DWORD PTR [rdx+rcx*4+0x18]
  eb:   f3 0f 58 44 8a 1c       addss  xmm0,DWORD PTR [rdx+rcx*4+0x1c]
  f1:   48 83 c1 08             add    rcx,0x8
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
  f5:   39 cd                   cmp    ebp,ecx
  f7:   7f c9                   jg     c2 <Java_JNITest6_averageN1+0xc2>
    {
        average += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
  f9:   4c 8b 03                mov    r8,QWORD PTR [rbx]
  fc:   f3 0f 11 44 24 0c       movss  DWORD PTR [rsp+0xc],xmm0
 102:   4c 89 e6                mov    rsi,r12
 105:   48 89 df                mov    rdi,rbx
 108:   31 c9                   xor    ecx,ecx
 10a:   41 ff 90 28 06 00 00    call   QWORD PTR [r8+0x628]
 
    /* vypocet prumeru */
    return average/length;
 111:   f3 0f 2a cd             cvtsi2ss xmm1,ebp
 115:   f3 0f 10 44 24 0c       movss  xmm0,DWORD PTR [rsp+0xc]
}
 11b:   48 83 c4 10             add    rsp,0x10
 11f:   5b                      pop    rbx
 120:   5d                      pop    rbp
 121:   41 5c                   pop    r12
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
 
    /* vypocet prumeru */
    return average/length;
 123:   f3 0f 5e c1             divss  xmm0,xmm1
}
 127:   c3                      ret
 128:   0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]
 12f:   00
 
0000000000000130 <Java_JNITest6_averageN2>:
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN2(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
 130:   41 54                   push   r12
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 132:   48 89 d6                mov    rsi,rdx
    /* vypocet prumeru */
    return average/length;
}
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN2(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
 135:   49 89 d4                mov    r12,rdx
 138:   55                      push   rbp
 139:   53                      push   rbx
 13a:   48 89 fb                mov    rbx,rdi
 13d:   48 83 ec 10             sub    rsp,0x10
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 141:   48 8b 07                mov    rax,QWORD PTR [rdi]
 144:   ff 90 58 05 00 00       call   QWORD PTR [rax+0x558]
 14a:   89 c5                   mov    ebp,eax
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 14c:   48 8b 03                mov    rax,QWORD PTR [rbx]
 14f:   31 d2                   xor    edx,edx
 151:   4c 89 e6                mov    rsi,r12
 154:   48 89 df                mov    rdi,rbx
 157:   ff 90 e8 05 00 00       call   QWORD PTR [rax+0x5e8]
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 15d:   85 ed                   test   ebp,ebp
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 15f:   48 89 c2                mov    rdx,rax
}
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN2(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 162:   0f 57 c0                xorps  xmm0,xmm0
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 165:   0f 8e be 00 00 00       jle    229 <Java_JNITest6_averageN2+0xf9>
 16b:   44 8d 4d ff             lea    r9d,[rbp-0x1]
    {
        average += *item;
 16f:   f3 0f 58 00             addss  xmm0,DWORD PTR [rax]
 173:   b9 01 00 00 00          mov    ecx,0x1
 178:   41 83 e1 07             and    r9d,0x7
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 17c:   83 fd 01                cmp    ebp,0x1
 17f:   0f 8e a4 00 00 00       jle    229 <Java_JNITest6_averageN2+0xf9>
 185:   45 85 c9                test   r9d,r9d
 188:   74 68                   je     1f2 <Java_JNITest6_averageN2+0xc2>
 18a:   41 83 f9 01             cmp    r9d,0x1
 18e:   74 55                   je     1e5 <Java_JNITest6_averageN2+0xb5>
 190:   41 83 f9 02             cmp    r9d,0x2
 194:   74 46                   je     1dc <Java_JNITest6_averageN2+0xac>
 196:   41 83 f9 03             cmp    r9d,0x3
 19a:   74 37                   je     1d3 <Java_JNITest6_averageN2+0xa3>
 19c:   41 83 f9 04             cmp    r9d,0x4
 1a0:   74 28                   je     1ca <Java_JNITest6_averageN2+0x9a>
 1a2:   41 83 f9 05             cmp    r9d,0x5
 1a6:   74 19                   je     1c1 <Java_JNITest6_averageN2+0x91>
 1a8:   41 83 f9 06             cmp    r9d,0x6
 1ac:   74 0a                   je     1b8 <Java_JNITest6_averageN2+0x88>
    {
        average += *item;
 1ae:   f3 0f 58 40 04          addss  xmm0,DWORD PTR [rax+0x4]
 1b3:   b9 02 00 00 00          mov    ecx,0x2
 1b8:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 1bd:   48 83 c1 01             add    rcx,0x1
 1c1:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 1c6:   48 83 c1 01             add    rcx,0x1
 1ca:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 1cf:   48 83 c1 01             add    rcx,0x1
 1d3:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 1d8:   48 83 c1 01             add    rcx,0x1
 1dc:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 1e1:   48 83 c1 01             add    rcx,0x1
 1e5:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 1ea:   48 83 c1 01             add    rcx,0x1
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 1ee:   39 cd                   cmp    ebp,ecx
 1f0:   7e 37                   jle    229 <Java_JNITest6_averageN2+0xf9>
    {
        average += *item;
 1f2:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 1f7:   f3 0f 58 44 8a 04       addss  xmm0,DWORD PTR [rdx+rcx*4+0x4]
 1fd:   f3 0f 58 44 8a 08       addss  xmm0,DWORD PTR [rdx+rcx*4+0x8]
 203:   f3 0f 58 44 8a 0c       addss  xmm0,DWORD PTR [rdx+rcx*4+0xc]
 209:   f3 0f 58 44 8a 10       addss  xmm0,DWORD PTR [rdx+rcx*4+0x10]
 20f:   f3 0f 58 44 8a 14       addss  xmm0,DWORD PTR [rdx+rcx*4+0x14]
 215:   f3 0f 58 44 8a 18       addss  xmm0,DWORD PTR [rdx+rcx*4+0x18]
 21b:   f3 0f 58 44 8a 1c       addss  xmm0,DWORD PTR [rdx+rcx*4+0x1c]
 221:   48 83 c1 08             add    rcx,0x8
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 225:   39 cd                   cmp    ebp,ecx
 227:   7f c9                   jg     1f2 <Java_JNITest6_averageN2+0xc2>
    {
        average += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
 229:   4c 8b 03                mov    r8,QWORD PTR [rbx]
 22c:   f3 0f 11 44 24 0c       movss  DWORD PTR [rsp+0xc],xmm0
 232:   4c 89 e6                mov    rsi,r12
 235:   48 89 df                mov    rdi,rbx
 238:   b9 02 00 00 00          mov    ecx,0x2
 23d:   41 ff 90 28 06 00 00    call   QWORD PTR [r8+0x628]
 
    /* vypocet prumeru */
    return average/length;
 244:   f3 0f 2a cd             cvtsi2ss xmm1,ebp
 248:   f3 0f 10 44 24 0c       movss  xmm0,DWORD PTR [rsp+0xc]
}
 24e:   48 83 c4 10             add    rsp,0x10
 252:   5b                      pop    rbx
 253:   5d                      pop    rbp
 254:   41 5c                   pop    r12
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
 
    /* vypocet prumeru */
    return average/length;
 256:   f3 0f 5e c1             divss  xmm0,xmm1
}
 25a:   c3                      ret
 25b:   0f 1f 44 00 00          nop    DWORD PTR [rax+rax*1+0x0]
 
0000000000000260 <Java_JNITest6_averageN3>:
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN3(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
 260:   41 54                   push   r12
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 262:   48 89 d6                mov    rsi,rdx
    /* vypocet prumeru */
    return average/length;
}
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN3(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
 265:   49 89 d4                mov    r12,rdx
 268:   55                      push   rbp
 269:   53                      push   rbx
 26a:   48 89 fb                mov    rbx,rdi
 26d:   48 83 ec 10             sub    rsp,0x10
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 271:   48 8b 07                mov    rax,QWORD PTR [rdi]
 274:   ff 90 58 05 00 00       call   QWORD PTR [rax+0x558]
 27a:   89 c5                   mov    ebp,eax
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 27c:   48 8b 03                mov    rax,QWORD PTR [rbx]
 27f:   31 d2                   xor    edx,edx
 281:   4c 89 e6                mov    rsi,r12
 284:   48 89 df                mov    rdi,rbx
 287:   ff 90 f0 06 00 00       call   QWORD PTR [rax+0x6f0]
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 28d:   85 ed                   test   ebp,ebp
 
    /* ziskat delku pole */
    jint length = (*jni_env)->GetArrayLength(jni_env, (jarray)array);
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 28f:   48 89 c2                mov    rdx,rax
}
 
JNIEXPORT jfloat JNICALL Java_JNITest6_averageN3(JNIEnv *jni_env, jclass klass, jfloatArray array)
{
    jint release_mode = JNI_ABORT;
    jfloat average = 0;
 292:   0f 57 c0                xorps  xmm0,xmm0
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 295:   0f 8e be 00 00 00       jle    359 <Java_JNITest6_averageN3+0xf9>
 29b:   44 8d 4d ff             lea    r9d,[rbp-0x1]
    {
        average += *item;
 29f:   f3 0f 58 00             addss  xmm0,DWORD PTR [rax]
 2a3:   b9 01 00 00 00          mov    ecx,0x1
 2a8:   41 83 e1 07             and    r9d,0x7
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 2ac:   83 fd 01                cmp    ebp,0x1
 2af:   0f 8e a4 00 00 00       jle    359 <Java_JNITest6_averageN3+0xf9>
 2b5:   45 85 c9                test   r9d,r9d
 2b8:   74 68                   je     322 <Java_JNITest6_averageN3+0xc2>
 2ba:   41 83 f9 01             cmp    r9d,0x1
 2be:   74 55                   je     315 <Java_JNITest6_averageN3+0xb5>
 2c0:   41 83 f9 02             cmp    r9d,0x2
 2c4:   74 46                   je     30c <Java_JNITest6_averageN3+0xac>
 2c6:   41 83 f9 03             cmp    r9d,0x3
 2ca:   74 37                   je     303 <Java_JNITest6_averageN3+0xa3>
 2cc:   41 83 f9 04             cmp    r9d,0x4
 2d0:   74 28                   je     2fa <Java_JNITest6_averageN3+0x9a>
 2d2:   41 83 f9 05             cmp    r9d,0x5
 2d6:   74 19                   je     2f1 <Java_JNITest6_averageN3+0x91>
 2d8:   41 83 f9 06             cmp    r9d,0x6
 2dc:   74 0a                   je     2e8 <Java_JNITest6_averageN3+0x88>
    {
        average += *item;
 2de:   f3 0f 58 40 04          addss  xmm0,DWORD PTR [rax+0x4]
 2e3:   b9 02 00 00 00          mov    ecx,0x2
 2e8:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 2ed:   48 83 c1 01             add    rcx,0x1
 2f1:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 2f6:   48 83 c1 01             add    rcx,0x1
 2fa:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 2ff:   48 83 c1 01             add    rcx,0x1
 303:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 308:   48 83 c1 01             add    rcx,0x1
 30c:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 311:   48 83 c1 01             add    rcx,0x1
 315:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 31a:   48 83 c1 01             add    rcx,0x1
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 31e:   39 cd                   cmp    ebp,ecx
 320:   7e 37                   jle    359 <Java_JNITest6_averageN3+0xf9>
    {
        average += *item;
 322:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 327:   f3 0f 58 44 8a 04       addss  xmm0,DWORD PTR [rdx+rcx*4+0x4]
 32d:   f3 0f 58 44 8a 08       addss  xmm0,DWORD PTR [rdx+rcx*4+0x8]
 333:   f3 0f 58 44 8a 0c       addss  xmm0,DWORD PTR [rdx+rcx*4+0xc]
 339:   f3 0f 58 44 8a 10       addss  xmm0,DWORD PTR [rdx+rcx*4+0x10]
 33f:   f3 0f 58 44 8a 14       addss  xmm0,DWORD PTR [rdx+rcx*4+0x14]
 345:   f3 0f 58 44 8a 18       addss  xmm0,DWORD PTR [rdx+rcx*4+0x18]
 34b:   f3 0f 58 44 8a 1c       addss  xmm0,DWORD PTR [rdx+rcx*4+0x1c]
 351:   48 83 c1 08             add    rcx,0x8
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f;
    int i;
    for (i=0; i<length; i++, item++)
 355:   39 cd                   cmp    ebp,ecx
 357:   7f c9                   jg     322 <Java_JNITest6_averageN3+0xc2>
    {
        average += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleasePrimitiveArrayCritical(jni_env, (jarray)array, (void*)f, release_mode);
 359:   4c 8b 03                mov    r8,QWORD PTR [rbx]
 35c:   f3 0f 11 44 24 0c       movss  DWORD PTR [rsp+0xc],xmm0
 362:   4c 89 e6                mov    rsi,r12
 365:   48 89 df                mov    rdi,rbx
 368:   b9 02 00 00 00          mov    ecx,0x2
 36d:   41 ff 90 f8 06 00 00    call   QWORD PTR [r8+0x6f8]
 
    /* vypocet prumeru */
    return average/length;
 374:   f3 0f 2a cd             cvtsi2ss xmm1,ebp
 378:   f3 0f 10 44 24 0c       movss  xmm0,DWORD PTR [rsp+0xc]
}
 37e:   48 83 c4 10             add    rsp,0x10
 382:   5b                      pop    rbx
 383:   5d                      pop    rbp
 384:   41 5c                   pop    r12
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleasePrimitiveArrayCritical(jni_env, (jarray)array, (void*)f, release_mode);
 
    /* vypocet prumeru */
    return average/length;
 386:   f3 0f 5e c1             divss  xmm0,xmm1
}
 38a:   c3                      ret

Opět je zajímavé se podívat na to, jak se překladači (který nyní neměl žádné informace o délce pole, na rozdíl od JITu!) podařilo rozbalit kritickou část kódu, tj. programovou smyčku pro výpočet sumy všech prvků pole. Kvůli rozbalení smyčky je poněkud složitější test na ukončení průchodu:

 2ac:   83 fd 01                cmp    ebp,0x1
 2af:   0f 8e a4 00 00 00       jle    359 <Java_JNITest6_averageN3+0xf9>
 2b5:   45 85 c9                test   r9d,r9d
 2b8:   74 68                   je     322 <Java_JNITest6_averageN3+0xc2>
 2ba:   41 83 f9 01             cmp    r9d,0x1
 2be:   74 55                   je     315 <Java_JNITest6_averageN3+0xb5>
 2c0:   41 83 f9 02             cmp    r9d,0x2
 2c4:   74 46                   je     30c <Java_JNITest6_averageN3+0xac>
 2c6:   41 83 f9 03             cmp    r9d,0x3
 2ca:   74 37                   je     303 <Java_JNITest6_averageN3+0xa3>
 2cc:   41 83 f9 04             cmp    r9d,0x4
 2d0:   74 28                   je     2fa <Java_JNITest6_averageN3+0x9a>
 2d2:   41 83 f9 05             cmp    r9d,0x5
 2d6:   74 19                   je     2f1 <Java_JNITest6_averageN3+0x91>
 2d8:   41 83 f9 06             cmp    r9d,0x6
 2dc:   74 0a                   je     2e8 <Java_JNITest6_averageN3+0x88>

…což je kombinováno s kódem pro přičtení posledních 0 až 7 prvků (povšimněte si cílů skoků v předchozím bloku instrukcí cmp (compare) a je (jump if equal):

 2de:   f3 0f 58 40 04          addss  xmm0,DWORD PTR [rax+0x4]
 2e3:   b9 02 00 00 00          mov    ecx,0x2
 2e8:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 2ed:   48 83 c1 01             add    rcx,0x1
 2f1:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 2f6:   48 83 c1 01             add    rcx,0x1
 2fa:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 2ff:   48 83 c1 01             add    rcx,0x1
 303:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 308:   48 83 c1 01             add    rcx,0x1
 30c:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 311:   48 83 c1 01             add    rcx,0x1
 315:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 31a:   48 83 c1 01             add    rcx,0x1

Vlastní smyčka je potom osmkrát rozbalena, takže vypadá prakticky stejně jako výsledek JITu (ten však rozbalil smyčku 16×, což už je ale zbytečně mnoho):

 322:   f3 0f 58 04 8a          addss  xmm0,DWORD PTR [rdx+rcx*4]
 327:   f3 0f 58 44 8a 04       addss  xmm0,DWORD PTR [rdx+rcx*4+0x4]
 32d:   f3 0f 58 44 8a 08       addss  xmm0,DWORD PTR [rdx+rcx*4+0x8]
 333:   f3 0f 58 44 8a 0c       addss  xmm0,DWORD PTR [rdx+rcx*4+0xc]
 339:   f3 0f 58 44 8a 10       addss  xmm0,DWORD PTR [rdx+rcx*4+0x10]
 33f:   f3 0f 58 44 8a 14       addss  xmm0,DWORD PTR [rdx+rcx*4+0x14]
 345:   f3 0f 58 44 8a 18       addss  xmm0,DWORD PTR [rdx+rcx*4+0x18]
 34b:   f3 0f 58 44 8a 1c       addss  xmm0,DWORD PTR [rdx+rcx*4+0x1c]
 351:   48 83 c1 08             add    rcx,0x8

7. Částečné řešení problému při sdílení polí – využití regionů

I přes optimalizace provedené GCC je však zřejmé, že kopie celých polí není a ani nemůže být optimálním řešením. Navíc platí, že u JNI funkcí GetPrimitiveArrayCritical()ReleasePrimitiveArrayCritical() není zaručeno, že se kopie neprovede. Ovšem v případě, že nativní část aplikace nemusí přistupovat k celému poli, ale pouze k jeho části, je možné provést kopii pouze této vybrané části, a to s využitím následujících JNI funkcí:

# Návratový typ Funkce Popis
1 void GetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize l, jboolean *buf) kopie prvků typu jboolean
2 void GetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf) kopie prvků typu jbyte
3 void GetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, jchar *buf) kopie prvků typu jchar
4 void GetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, jshort *buf) kopie prvků typu jshort
5 void GetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, jint *buf) kopie prvků typu jint
6 void GetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, jlong *buf) kopie prvků typu jlong
7 void GetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, jfloat *buf) kopie prvků typu jfloat
8 void GetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, jdouble *buf) kopie prvků typu jdouble

Největším rozdílem oproti všem výše popsaným funkcím je fakt, že se programátor musí sám postarat (funkcí malloc() apod.) o alokaci paměti dostatečně velkou na to, aby pojala len prvků daného typu a samozřejmě je nutné tento paměťový region opět uvolnit funkcí free().

Pokud je nutné prvky zapsat zpět do pole sdíleného s javovskou částí aplikace, použijí se následující funkce:

# Návratový typ Funkce Popis
1 void SetBooleanArrayRegion(JNIEnv *env, jbooleanArray array, jsize start, jsize l, const jboolean *buf) kopie prvků typu jboolean
2 void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const jbyte *buf) kopie prvků typu jbyte
3 void SetCharArrayRegion(JNIEnv *env, jcharArray array, jsize start, jsize len, const jchar *buf) kopie prvků typu jchar
4 void SetShortArrayRegion(JNIEnv *env, jshortArray array, jsize start, jsize len, const jbyte jshort *buf) kopie prvků typu jshort
5 void SetIntArrayRegion(JNIEnv *env, jintArray array, jsize start, jsize len, const jbyte jint *buf) kopie prvků typu jint
6 void SetLongArrayRegion(JNIEnv *env, jlongArray array, jsize start, jsize len, const jbyte jlong *buf) kopie prvků typu jlong
7 void SetFloatArrayRegion(JNIEnv *env, jfloatArray array, jsize start, jsize len, const jbyte jfloat *buf) kopie prvků typu jfloat
8 void SetDoubleArrayRegion(JNIEnv *env, jdoubleArray array, jsize start, jsize len, const jbyte jdouble *buf) kopie prvků typu jdouble

8. Benchmark pracující pouze s částí sdíleného pole

Nyní si již můžeme na dalším benchmarku vyzkoušet, co se stane, pokud budeme chtít vypočítat sumu jen z těch prvků, které tvoří pouze část pole. Tato situace nastává poměrně často, například při zpracování obrazu či jiného signálu. Dnešní druhý benchmark se v mnoha ohledech podobá benchmarku prvnímu, větší změny však nastanou v nativní části aplikace:

/**
 * Jednoduchy benchmark pro porovnani rychlosti pristupu k vybranym
 * prvkum poli v nativnich metodach/funkcich.
 *
 * @author Pavel Tisnovsky
 */
public class JNITest7 {
 
    /**
     * Pocet opakovani zahrivaci faze benchmarku.
     */
    private static final int WARMUP_ITERS = 15000;
 
    /**
     * Pocet opakovani merene faze benchmarku.
     */
    private static final int BENCHMARK_ITERS = 15000;
 
    /**
     * Velikost pole, ktere bude pouzito v benchmarku.
     */
    private static final int ARRAY_SIZE = 200000;
 
    /**
     * Prvni zpracovavany prvek pole.
     */
    private static final int FIRST_ARRAY_ITEM = ARRAY_SIZE / 2;
 
    /**
     * Posledni zpracovavany prvek pole.
     */
    private static final int LAST_ARRAY_ITEM = 2 * ARRAY_SIZE / 3;
    //private static final int LAST_ARRAY_ITEM = FIRST_ARRAY_ITEM + 100;
 
    /**
     * Testovaci pole.
     */
    static float[] array = new float[ARRAY_SIZE];
 
    /**
     * Naplneni pole daty.
     */
    static {
        for (int i=0; i < ARRAY_SIZE; i++) {
            array[i] = (float)(i+1);
        }
    }
 
    /**
     * Nativni metody testovane benchmarkem.
     */
    native public static float sumN1(float[] array, int first_item, int last_item);
    native public static float sumN2(float[] array, int first_item, int last_item);
    native public static float sumN3(float[] array, int first_item, int last_item);
    native public static float sumN4(float[] array, int first_item, int last_item);
 
    /**
     * Obdobna metoda napsana v Jave.
     */
    public static float sumJ(float[] array, int first_item, int last_item) {
        float sum = 0;
 
        // pruchod polem a vypocet sumy prvku
        for (int i=first_item; i < last_item; i++) {
            sum += array[i];
        }
 
        // vraceni vysledku
        return sum;
    }
 
    /**
     * Spusteni benchmarku.
     */
    private static void runJNIBenchmarks() {
        warmup();
        benchmark();
    }
 
    /**
     * Vypis vypocteneho vysledku (jen pro kontrolu).
     */
    private static void printResult(float result) {
        System.out.print("    result=");
        System.out.println(result);
    }
 
    /**
     * Zahrivaci faze benchmarku.
     */
    private static void warmup() {
        System.out.println("Warmup phase...");
        float result;
 
        result = 0;
        // donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = sumJ(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        printResult(result);
 
        result = 0;
        // taktez zde donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = sumN1(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        printResult(result);
 
        result = 0;
        // taktez zde donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = sumN2(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        printResult(result);
 
        result = 0;
        // taktez zde donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = sumN3(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        printResult(result);
 
        result = 0;
        // taktez zde donutime JIT k prekladu
        for (int i = 0; i < WARMUP_ITERS; i++) {
            result = sumN4(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        printResult(result);
 
        System.out.println("done");
    }
 
    /**
     * Vlastni mereny benchmark.
     */
    private static void benchmark() {
        System.out.println("Benchmark phase...");
        long t1, t2, delta_t;
        float result;
 
        // provest test a zmerit cas behu prvniho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = sumJ(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro prvni test
        System.out.format("JITted method time:      %,12d ns\n", delta_t);
 
        // provest test a zmerit cas behu druheho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = sumN1(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro druhy test
        System.out.format("native function #1 time: %,12d ns\n", delta_t);
 
        // provest test a zmerit cas behu tretiho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = sumN2(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro treti test
        System.out.format("native function #2 time: %,12d ns\n", delta_t);
 
        // provest test a zmerit cas behu ctvrteho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = sumN3(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro ctvrty test
        System.out.format("native function #3 time: %,12d ns\n", delta_t);
 
        // provest test a zmerit cas behu pateho testu
        t1 = System.nanoTime();
        result = 0;
        for (int i = 0; i < BENCHMARK_ITERS; i++) {
            result = sumN4(array, FIRST_ARRAY_ITEM, LAST_ARRAY_ITEM);
        }
        t2 = System.nanoTime();
        delta_t = t2 - t1;
        printResult(result);
        // vypis casu pro paty test
        System.out.format("native function #4 time: %,12d ns\n", delta_t);
 
        System.out.println("done");
    }
 
    /**
     * Spusteni benchmarku.
     */
    public static void main(String[] args) {
        System.loadLibrary("JNITest7");
        runJNIBenchmarks();
    }
}

Hlavičkový soubor pro nativní metody:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest7 */
 
#ifndef _Included_JNITest7
#define _Included_JNITest7
#ifdef __cplusplus
extern "C" {
#endif
#undef JNITest7_WARMUP_ITERS
#define JNITest7_WARMUP_ITERS 15000L
#undef JNITest7_BENCHMARK_ITERS
#define JNITest7_BENCHMARK_ITERS 15000L
#undef JNITest7_ARRAY_SIZE
#define JNITest7_ARRAY_SIZE 200000L
#undef JNITest7_FIRST_ARRAY_ITEM
#define JNITest7_FIRST_ARRAY_ITEM 100000L
#undef JNITest7_LAST_ARRAY_ITEM
#define JNITest7_LAST_ARRAY_ITEM 200000L
/*
 * Class:     JNITest7
 * Method:    sumN1
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN1
  (JNIEnv *, jclass, jfloatArray, jint, jint);
 
/*
 * Class:     JNITest7
 * Method:    sumN2
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN2
  (JNIEnv *, jclass, jfloatArray, jint, jint);
 
/*
 * Class:     JNITest7
 * Method:    sumN3
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN3
  (JNIEnv *, jclass, jfloatArray, jint, jint);
 
/*
 * Class:     JNITest7
 * Method:    sumN4
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN4
  (JNIEnv *, jclass, jfloatArray, jint, jint);
 
#ifdef __cplusplus
}
#endif
#endif

Nativní metody měřené benchmarkem jsou čtyři a mají následující vlastnosti:

Metoda Popis
sumN1() používá GetFloatArrayElements()ReleaseFloatArrayElements() se zpětnou kopií prvků
sumN2() používá GetFloatArrayElements()ReleaseFloatArrayElements() bez zpětné kopie prvků
sumN3() používá GetPrimitiveArrayCritical()ReleasePrimitiveArrayCritical()
sumN4() používá GetFloatArrayRegion() s ruční alokací a dealokací paměti

Podívejme se tedy na celý kód nativní části aplikace:

#include <stdlib.h>
#include "JNITest7.h"
 
/*
 * Class:     JNITest7
 * Method:    sumN1
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN1(
        JNIEnv      *jni_env,
        jclass      klass,
        jfloatArray array,
        jint        first_item,
        jint        last_item)
{
    jint release_mode = 0;
    jfloat sum = 0;
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f+first_item;
    int i;
    for (i=first_item; i<last_item; i++, item++)
    {
        sum += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
 
    /* vraceni vypoctene sumy */
    return sum;
}
 
/*
 * Class:     JNITest7
 * Method:    sumN2
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN2(
        JNIEnv      *jni_env,
        jclass      klass,
        jfloatArray array,
        jint        first_item,
        jint        last_item)
{
    jint release_mode = JNI_ABORT;
    jfloat sum = 0;
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (*jni_env)->GetFloatArrayElements(jni_env, array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f+first_item;
    int i;
    for (i=first_item; i<last_item; i++, item++)
    {
        sum += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleaseFloatArrayElements(jni_env, array, f, release_mode);
 
    /* vraceni vypoctene sumy */
    return sum;
}
 
/*
 * Class:     JNITest7
 * Method:    sumN3
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN3(
        JNIEnv      *jni_env,
        jclass      klass,
        jfloatArray array,
        jint        first_item,
        jint        last_item)
{
    jint release_mode = JNI_ABORT;
    jfloat sum = 0;
 
    /* prevod na "ceckove" pole prvku typu float */
    jfloat *f = (jfloat*) (*jni_env)->GetPrimitiveArrayCritical(jni_env, (jarray)array, NULL);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = f+first_item;
    int i;
    for (i=first_item; i<last_item; i++, item++)
    {
        sum += *item;
    }
 
    /* uvolnit ceckove pole s pripadnou zpetnou kopii prvku */
    (*jni_env)->ReleasePrimitiveArrayCritical(jni_env, (jarray)array, (void*)f, release_mode);
 
    /* vraceni vypoctene sumy */
    return sum;
}
 
/*
 * Class:     JNITest7
 * Method:    sumN4
 * Signature: ([FII)F
 */
JNIEXPORT jfloat JNICALL Java_JNITest7_sumN4(
        JNIEnv      *jni_env,
        jclass      klass,
        jfloatArray array,
        jint        first_item,
        jint        last_item)
{
    jfloat sum = 0;
 
    /* alokace pole */
    jfloat *c_array = (jfloat*)malloc((sizeof(jfloat)) * (last_item-first_item));
 
    /* kopie pole */
    (*jni_env)->GetFloatArrayRegion(jni_env, array, first_item, last_item-first_item, c_array);
 
    /* pruchod polem a vypocet sumy prvku */
    jfloat *item = c_array;
    int i;
    for (i=first_item; i<last_item; i++, item++)
    {
        sum += *item;
    }
 
    /* alokovane pole je zapotrebi uvolnit */
    free(c_array);
 
    /* vraceni vypoctene sumy */
    return sum;
}

9. Překlad a spuštění benchmarku

V rychlosti si řekněme jak probíhá překlad.

Javovská část aplikace:

javac JNITest7.java

Vygenerování hlavičkového souboru:

javah JNITest7

Překlad nativní části aplikace (jeden ze způsobů):

gcc -O3 -funroll-all-loops -shared \
                 -I/usr/lib/jvm/java-1.7.0-openjdk/include/ \
                 -o libJNITest7.so JNITest7.c
 
gcc -O3 -funroll-all-loops -shared -fPIC \
                  -I/usr/lib/jvm/java-1.7.0-openjdk/include/ \
                  -I/usr/lib/jvm/java-1.7.0-openjdk/include/linux \
                  -o libJNITest7.so JNITest7.c
 
gcc -O3 -funroll-all-loops -shared -I"c:\Program Files\Java\jdk1.7.0_25\include" -o JNITest4.dll JNITest4.c

10. Výsledky benchmarku

Výsledky benchmarku si uvedeme pro dva případy. V prvním případě se suma počítala pro poměrně velkou část pole, kde první a poslední prvek byly určeny konstantami:

    private static final int FIRST_ARRAY_ITEM = ARRAY_SIZE / 2;
    private static final int LAST_ARRAY_ITEM = 2 * ARRAY_SIZE / 3;
Warmup phase...
    result=1.49999555E10
    result=1.49999555E10
    result=1.49999555E10
    result=1.49999555E10
    result=1.49999555E10
done
Benchmark phase...
    result=1.49999555E10
JITted method time:      1,255,174,174 ns
    result=1.49999555E10
native function #1 time: 5,426,085,733 ns
    result=1.49999555E10
native function #2 time: 4,423,321,655 ns
    result=1.49999555E10
native function #3 time: 1,256,383,554 ns
    result=1.49999555E10
native function #4 time: 2,448,692,621 ns
done

Již zde je patrné, jak je kopie celých polí ve funkcích sumN1()sumN2(), poměrně neefektivní je i kopie části pole ve funkci sumN4.

Pro porovnání se podívejme na to, co se stane v případě, že se suma bude počítat pro pouhých sto prvků pole:

    private static final int FIRST_ARRAY_ITEM = ARRAY_SIZE / 2;
    private static final int LAST_ARRAY_ITEM = FIRST_ARRAY_ITEM + 100;

Zde již samozřejmě budou časy mnohem odlišnější, a to u těch metod a funkcí, které nemusí kopírovat celá pole:

Warmup phase...
    result=1.000505E7
    result=1.000505E7
    result=1.000505E7
    result=1.000505E7
    result=1.000505E7
done
Benchmark phase...
    result=1.000505E7
JITted method time:            922,809 ns
    result=1.000505E7
native function #1 time: 4,163,715,458 ns
    result=1.000505E7
native function #2 time: 3,151,356,483 ns
    result=1.000505E7
native function #3 time:     1,676,210 ns
    result=1.000505E7
native function #4 time:     2,357,485 ns
done

Ponaučení: rozhraní JNI sice může v některých případech pomoci při optimalizaci aplikací, ale nelze se v žádném případě spoléhat na to, že jeho použití je samospasitelné. Problematickým místem je například přenos polí mezi javovskou a nativní částí aplikace, kdy (mnohdy vynucená) kopie prvků polí zcela znehodnotí jakékoli výhody, které by z použití nativního kódu mohly vyplynout.

Ponaučení2: používat mikrobencharky a profiler jako základní nástroje ještě před prováděním jakýchkoli optimalizací.

11. Repositář se zdrojovými soubory i se skripty pro překlad a spuštění

Následuje – v tomto seriálu již tradiční – kapitola s odkazy na zdrojové kódy uložené do Mercurial repositáře. V následující tabulce najdete odkazy na prozatím nejnovější verzi dnes použitých demonstračních příkladů (benchmarků):

# Zdrojový soubor/skript Umístění souboru v repositáři
1 JNITest6.c http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c78270233554/jit/JNI­Test6/JNITest6.c
2 JNITest6.h http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c78270233554/jit/JNI­Test6/JNITest6.h
3 JNITest6.java http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c78270233554/jit/JNI­Test6/JNITest6.java
     
4 compileJNITest6.sh http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c78270233554/jit/JNI­Test6/compileJNITest6.sh
5 compileJNITest6.bat http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c78270233554/jit/JNI­Test6/compileJNITest6.bat
6 runJNITest6.sh http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c78270233554/jit/JNI­Test6/runJNITest6.sh
7 runJNITest6.bat http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/c78270233554/jit/JNI­Test6/runJNITest6.bat
     
8 toAssembly.sh http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test6/toAssembly.sh
9 toAssembly2.sh http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test6/toAssembly2.sh
     
     
10 JNITest7.c http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test7/JNITest7.c
11 JNITest7.h http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test7/JNITest7.h
12 JNITest7.java http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test7/JNITest7.java
     
13 compileJNITest7.sh http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test7/compileJNITest7.sh
14 compileJNITest7.bat http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test7/compileJNITest7.bat
15 runJNITest7.sh http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test7/runJNITest7.sh
16 runJNITest7.bat http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/be339062ceed/jit/JNI­Test7/runJNITest7.bat

12. Odkazy na Internetu

  1. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  2. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  3. MultiMedia eXtensions
    http://softpixel.com/~cwrig­ht/programming/simd/mmx.phpi
  4. SSE (Streaming SIMD Extentions)
    http://www.songho.ca/misc/sse/sse­.html
  5. Timothy A. Chagnon: SSE and SSE2
    http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf
  6. Intel corporation: Extending the Worldr's Most Popular Processor Architecture
    http://download.intel.com/techno­logy/architecture/new-instructions-paper.pdf
  7. SIMD architectures:
    http://arstechnica.com/ol­d/content/2000/03/simd.ar­s/
  8. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  9. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  10. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  11. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  12. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  13. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  14. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  15. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  16. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  17. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  18. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  19. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  20. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  21. ASM Home page
    http://asm.ow2.org/
  22. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  23. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  24. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  25. BCEL Home page
    http://commons.apache.org/bcel/
  26. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  27. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  28. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  29. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  30. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  31. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  32. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  33. Javassist
    http://www.jboss.org/javassist/
  34. Byteman
    http://www.jboss.org/byteman
  35. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  36. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  37. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  38. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  39. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  40. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  41. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  42. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  43. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  44. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  45. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  46. Cobertura
    http://cobertura.sourceforge.net/
  47. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html
Našli jste v článku chybu?

19. 11. 2013 14:59

Je to tak a jsem rad, ze si toho nekdo vsiml (coz znamena, ze u vas na fakulte to asi nekdo vysvetlil, coz kupodivu neni zvykem vsude :-).

U FP obecne neplati napriklad vztah (a+b)-b = a + (b-b) atd., tady to urcite vede k pocetni chybe.

Ja jsem pouzil float[] schvalne, a to z toho duvodu, aby se mohl jak JIT, tak i GCC "vyradit" na pouziti SSE, nicmene neni problem to prepsat na pouziti longu.

Jinak typ float je v Jave vzdy IEEE 754 single, konkretni vysledky operaci se sice mohou lisit, ale…

19. 11. 2013 14:45

Chápu pokus o relativní jednoduchost příkladů. Ale je trochu nefér porovnávat nativní kód bez optimalizace na moderní procesor s optimalizací na moderní procesor přes JIT. Samozřejmě, test je poměrně dost syntetický a asi nedává příliš smysl řešit složitě inicializaci různých kompilovaných knihoven podle procesoru na tomto příkladu. Nicméně kompilace pro procesory s MMX a SSE instrukcemi by mohl celkem pomoct. Zkuste si schálně přidat k GCC parametrům ještě -march=native. Sice se tento kód tímto…

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

Podnikatel.cz: Babiše přesvědčila 89letá podnikatelka?!

Babiše přesvědčila 89letá podnikatelka?!

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Root.cz: Pinebook: linuxový notebook za 89 dolarů

Pinebook: linuxový notebook za 89 dolarů

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Měšec.cz: Jak vymáhat výživné zadarmo?

Jak vymáhat výživné zadarmo?

Podnikatel.cz: K EET. Štamgast už peníze na stole nenechá

K EET. Štamgast už peníze na stole nenechá

DigiZone.cz: ČT má dalšího zástupce v EBU

ČT má dalšího zástupce v EBU

Měšec.cz: Jak levně odeslat balík přímo z domu?

Jak levně odeslat balík přímo z domu?

Podnikatel.cz: 1. den EET? Problémy s pokladnami

1. den EET? Problémy s pokladnami

Vitalia.cz: Mondelez stahuje rizikovou čokoládu Milka

Mondelez stahuje rizikovou čokoládu Milka

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

120na80.cz: Jak oddálit Alzheimera?

Jak oddálit Alzheimera?

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

DigiZone.cz: Flix TV má set-top box s HEVC

Flix TV má set-top box s HEVC

Vitalia.cz: Spor o mortadelu: podle Lidlu falšovaná nebyla

Spor o mortadelu: podle Lidlu falšovaná nebyla

Vitalia.cz: Jmenuje se Janina a žije bez cukru

Jmenuje se Janina a žije bez cukru

Lupa.cz: Teletext je „internetem hipsterů“

Teletext je „internetem hipsterů“