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
11. Repositář se zdrojovými soubory i se skripty pro překlad a spuštění
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() a 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() a 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() a ReleaseFloatArrayElements() se zpětnou kopií, nejrychlejší je nativní funkce využívající GetPrimitiveArrayCritical() a 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() a 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() a ReleaseFloatArrayElements() se zpětnou kopií prvků |
| sumN2() | používá GetFloatArrayElements() a ReleaseFloatArrayElements() bez zpětné kopie prvků |
| sumN3() | používá GetPrimitiveArrayCritical() a 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() a 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ů):
12. Odkazy na Internetu
- Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - MultiMedia eXtensions
http://softpixel.com/~cwright/programming/simd/mmx.phpi - SSE (Streaming SIMD Extentions)
http://www.songho.ca/misc/sse/sse.html - Timothy A. Chagnon: SSE and SSE2
http://www.cs.drexel.edu/~tc365/mpi-wht/sse.pdf - Intel corporation: Extending the Worldr's Most Popular Processor Architecture
http://download.intel.com/technology/architecture/new-instructions-paper.pdf - SIMD architectures:
http://arstechnica.com/old/content/2000/03/simd.ars/ - GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html