Obsah
1. Pohled pod kapotu JVM – základy optimalizace aplikací naprogramovaných v Javě (záhadná funkce System.arraycopy)
2. Testovací příklad ArrayCopyTest.java
3. Bajtkód testovacího příkladu ArrayCopyTest.java
4. Výpis informací o metodách přeložených JITem
5. Výpis přeložených metod s voláním intrinsic
6. Dvě varianty implementace metody System.arraycopy() při použití int[]
7. Benchmark ArrayCopyTest2.java
8. Výsledky běhu benchmarku
9. Volby UseXMMForArrayCopy a UseUnalignedLoadStores a jejich vliv na System.arraycopy
10. Obsah následující části seriálu
11. Repositář se všemi testovacími příklady
12. Odkazy na Internetu
1. Pohled pod kapotu JVM – základy optimalizace aplikací naprogramovaných v Javě (záhadná funkce System.arraycopy)
V předchozí části tohoto seriálu jsme si mj. řekli, že v Hotspotu i v dalších typech virtuálních strojů Javy se používá optimalizace založená na takzvaných intrinsic. Tato optimalizační technika vychází z myšlenky, že některé standardní metody jsou používané velmi často a současně se jedná o metody, v nichž jsou implementovány časově náročné činnosti, takže by bylo vhodné, aby byly tyto metody již dopředu implementovány a optimalizovány pro každou mikroprocesorovou architekturu, na které bude provozován virtuální stroj Javy. Typickými metodami a funkcemi, které je vhodné již dopředu optimalizovat, jsou některé goniometrické a logaritmické funkce, které můžeme najít ve třídě java.lang.Math. Implementace je v tomto případě většinou velmi jednoduchá, neboť se pouze zavolá příslušná instrukce matematického koprocesoru – což mj. znamená i automatický inlining. Jako příklad jsme si uvedli funkci sinus, kterou Hotspot „přeloží“ následujícím způsobem:
void Assembler::fsin() {
emit_byte(0xD9);
emit_byte(0xFE);
}
Bajt s hodnotou 0×D9 značí prefix ESC 1 pro vyvolání instrukce matematického koprocesoru, druhý bajt s hodnotou 0×FE je již vlastní instrukce FSIN očekávající operand v registru ST(0).
My se dnes budeme zabývat především funkcí System.arraycopy(), a to z toho důvodu, že je tato funkce často využívána jak v aplikacích, tak i přímo ve standardních knihovnách Javy. O tom se lze ostatně snadno přesvědčit jednoduchou analýzou souboru src.zip, který je uložený v adresáři /usr/lib/jvm/java-{verze}/:
unzip -c /usr/lib/jvm/java-1.7.0-openjdk/src.zip | grep System.arraycopy | wc -l
Mělo by se vrátit přibližně tisíc údajů.
Poznámka: termín „funkce“ zde používám záměrně, protože System.arraycopy() je statická a současně i veřejná metoda, tudíž má blíže ke klasicky pojatým funkcím, než k OOP metodám :-)
2. Testovací příklad ArrayCopyTest.java
Chování funkce System.arraycopy() dnes budeme studovat na několika demonstračních příkladech. První příklad je velmi jednoduchý. Funkce System.arraycopy() je zde použita pro kopii prvků polí celých čísel (int[]), a to jak mezi dvěma rozdílnými poli, tak i mezi stejným polem (což je dovoleno). Navíc se mění i offsety prvního zdrojového popř. prvního cílového prvku. Jak uvidíme dále, jsou tyto údaje zpracovávány JIT překladačem, který na základě těchto údajů správně zvolí vhodnou implementaci funkce System.arraycopy():
/**
* Jednoduchy testovaci program volajici funkci
* @link System#arraycopy(java.lang.Object, int, java.lang.Object, int, int)
* Jako zdrojove a cilove pole jsou pouzita pole celych cisel int[].
* Pro volani se pouzivaji ruzne kombinace offsetu a referenci na zdrojove
* a cilove pole.
*
* @author Pavel Tisnovsky
*/
public class ArrayCopyTest {
static int[] src = new int[50000];
static int[] dest = new int[50000];
/** Kopie mezi rozdilnymi poli, oba offsety jsou nulove */
public static void testArrayCopy1(int offset, int length) {
System.arraycopy(src, 0, dest, 0, length);
}
/** Kopie mezi rozdilnymi poli, nulovy je jen druhy offset */
public static void testArrayCopy2(int offset, int length) {
System.arraycopy(src, offset, dest, 0, length);
}
/** Kopie mezi rozdilnymi poli, nulovy je jen prvni offset */
public static void testArrayCopy3(int offset, int length) {
System.arraycopy(src, 0, dest, offset, length);
}
/** Kopie prvku v jednom poli, oba offsety jsou nulove */
public static void testArrayCopy4(int offset, int length) {
System.arraycopy(src, 0, src, 0, length);
}
/** Kopie prvku v jednom poli, nulovy je jen druhy offset */
public static void testArrayCopy5(int offset, int length) {
System.arraycopy(src, offset, src, 0, length);
}
/** Kopie prvku v jednom poli, nulovy je jen prvni offset */
public static void testArrayCopy6(int offset, int length) {
System.arraycopy(src, 0, src, offset, length);
}
public static void main(String[] args) {
// donutime JIT k prekladu, soucasne se vsak neprekroci
// meze poli
for (int i = 0; i < 20000; i++) {
testArrayCopy1(i, i);
testArrayCopy2(i, i);
testArrayCopy3(i, i);
testArrayCopy4(i, i);
testArrayCopy5(i, i);
testArrayCopy6(i, i);
}
}
}
3. Bajtkód testovacího příkladu ArrayCopyTest.java
V předchozích částech tohoto seriálu jsme si již několikrát řekli, že překladač Javy, tj. nástroj javac si s prakticky žádnými optimalizacemi hlavu neláme :-), takže pro nás již nebude takovým překvapením fakt, že se všech šest metod testArrayCopyX() přeloží prakticky stejným způsobem – přesněji řečeno budou bajtkódy metod testArrayCopy1() až testArrayCopy3() shodné, stejně jako budou identické bajtkódy metod testArrayCopy4() až testArrayCopy6(). Ve všech těchto metodách se nejprve na zásobník operandů uloží všech pět argumentů funkce System.arraycopy() a posléze dojde k zavolání této funkce přes instrukci invokestatic:
Compiled from "ArrayCopyTest.java"
public class ArrayCopyTest {
static int[] src;
static int[] dest;
public ArrayCopyTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void testArrayCopy1(int, int);
Code:
0: getstatic #2 // Field src:[I
3: iconst_0
4: getstatic #3 // Field dest:[I
7: iconst_0
8: iload_1
9: invokestatic #4 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
12: return
public static void testArrayCopy2(int, int);
Code:
0: getstatic #2 // Field src:[I
3: iload_0
4: getstatic #3 // Field dest:[I
7: iconst_0
8: iload_1
9: invokestatic #4 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
12: return
public static void testArrayCopy3(int, int);
Code:
0: getstatic #2 // Field src:[I
3: iconst_0
4: getstatic #3 // Field dest:[I
7: iload_0
8: iload_1
9: invokestatic #4 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
12: return
public static void testArrayCopy4(int, int);
Code:
0: getstatic #2 // Field src:[I
3: iconst_0
4: getstatic #2 // Field src:[I
7: iconst_0
8: iload_1
9: invokestatic #4 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
12: return
public static void testArrayCopy5(int, int);
Code:
0: getstatic #2 // Field src:[I
3: iload_0
4: getstatic #2 // Field src:[I
7: iconst_0
8: iload_1
9: invokestatic #4 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
12: return
public static void testArrayCopy6(int, int);
Code:
0: getstatic #2 // Field src:[I
3: iconst_0
4: getstatic #2 // Field src:[I
7: iload_0
8: iload_1
9: invokestatic #4 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
12: return
public static void main(java.lang.String[]);
Code:
0: iconst_0
1: istore_1
2: iload_1
3: sipush 20000
6: if_icmpge 45
9: iload_1
10: iload_1
11: invokestatic #5 // Method testArrayCopy1:(II)V
14: iload_1
15: iload_1
16: invokestatic #6 // Method testArrayCopy2:(II)V
19: iload_1
20: iload_1
21: invokestatic #7 // Method testArrayCopy3:(II)V
24: iload_1
25: iload_1
26: invokestatic #8 // Method testArrayCopy4:(II)V
29: iload_1
30: iload_1
31: invokestatic #9 // Method testArrayCopy5:(II)V
34: iload_1
35: iload_1
36: invokestatic #10 // Method testArrayCopy6:(II)V
39: iinc 1, 1
42: goto 2
45: return
static {};
Code:
0: ldc #11 // int 50000
2: newarray int
4: putstatic #2 // Field src:[I
7: ldc #11 // int 50000
9: newarray int
11: putstatic #3 // Field dest:[I
14: return
}
4. Výpis informací o metodách přeložených JITem
Informaci o „statickém“ chování již tedy známe, nyní se ovšem musíme podívat na to, jakým způsobem bude funkce System.arraycopy() zpracována při běhu aplikace, což je mnohem zajímavější a současně i důležitější než samotný obsah bajtkódu (ten se v ideálním případě nahradí JITovaným strojovým kódem). Pro informaci o tom, ve kterém okamžiku došlo k překladu nějaké funkce či metody, můžeme použít nám již známý přepínač -XX:+PrintCompilation, který je nutno použít společně s přepínačem -XX:+UnlockDiagnosticVMOptions. Navíc ještě přepneme spouštěný virtuální stroj do režimu používání JIT překladače typu server a přesně nastavíme počet opakování smyčky/volání metod tak, aby byl překlad zahájen již po 10000 iterací:
java -server -XX:CompileThreshold=10000 -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation ArrayCopyTest
206 1 n java.lang.System::arraycopy (0 bytes) (static)
1066 2 ArrayCopyTest::testArrayCopy1 (13 bytes)
1079 3 ArrayCopyTest::testArrayCopy2 (13 bytes)
1082 4 ArrayCopyTest::testArrayCopy3 (13 bytes)
1085 5 ArrayCopyTest::testArrayCopy4 (13 bytes)
1087 6 ArrayCopyTest::testArrayCopy5 (13 bytes)
1089 7 ArrayCopyTest::testArrayCopy6 (13 bytes)
1824 1 % ArrayCopyTest::main @ 2 (46 bytes)
Kromě plných jmen přeložených metod a funkcí se ještě před těmito jmény objevují prapodivné znaky, které nám však mohou napomoci k pochopení, jakým způsobem je která metoda či funkce přeložena. Význam některých znaků je vypsán v následující tabulce:
Znak |
Význam |
s |
synchronizovaná metoda |
! |
metoda s handlerem obsluhy výjimek |
n |
nativní metoda, popř. intrinsic
|
% |
probíhá on stack replacement, což znamená, že metoda byla zkompilována, ale ještě neukončena |
* |
wrapper pro nativní kód |
Můžeme vidět, že u překládané funkce System.arraycopy() je uveden znak „n“ značící nativní metodu a/nebo intrinsic. U metody main() je naproti tomu znak „%“ značící on stack replacement (zde je to pochopitelné).
5. Výpis přeložených metod s voláním intrinsic
Základní informaci o tom, které funkce a metody byly přeloženy, tedy již máme, ovšem zatím vůbec nevíme, jak vypadá konkrétní volání System.arraycopy(). Zde již nezbývá nic jiného, než se podívat na generovaný strojový kód, a to opět s využitím přepínače -XX:+UnlockDiagnosticVMOptions, tentokráte použitého společně s přepínačem -XX:+PrintAssembly. Spuštění virtuálního stroje bude vypadat následovně:
java -server -XX:CompileThreshold=10000 -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly ArrayCopyTest
Na architektuře i386 dostaneme následující výpis (rozdělím ho do šesti částí, pro každou testovací metodu jednu):
ArrayCopyTest.testArrayCopy1
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'testArrayCopy1' '(II)V' in 'ArrayCopyTest'
# parm0: ecx = int
# parm1: edx = int
# [sp+0x20] (sp of caller)
0x009bad00: mov %eax,0xffffc000(%esp)
0x009bad07: push %ebp
0x009bad08: sub $0x18,%esp ;*synchronization entry
; - ArrayCopyTest::testArrayCopy1@-1 (line 7)
0x009bad0b: mov $0x3b812f8,%ebx ; {oop(a 'java/lang/Class' = 'ArrayCopyTest')}
0x009bad10: mov 0x70(%ebx),%ecx ;*getstatic src
; - ArrayCopyTest::testArrayCopy1@0 (line 7)
0x009bad13: mov 0x74(%ebx),%ebp ;*getstatic dest
; - ArrayCopyTest::testArrayCopy1@4 (line 7)
0x009bad16: mov 0x8(%ecx),%ebx ; implicit exception: dispatches to 0x009bad70
0x009bad19: mov 0x8(%ebp),%edi ; implicit exception: dispatches to 0x009bad81
0x009bad1c: cmp %edx,%ebx
0x009bad1e: jb 0x009bad4b
0x009bad20: cmp %edx,%edi
0x009bad22: jb 0x009bad4f
0x009bad24: mov %edx,%edi
0x009bad26: test %edx,%edx
0x009bad28: jle 0x009bad6a
0x009bad2a: lea 0xc(%ebp),%ebx
0x009bad2d: lea 0xc(%ecx),%ebp
0x009bad30: mov %ebp,(%esp)
0x009bad33: mov %ebx,0x4(%esp)
0x009bad37: mov %edx,0x8(%esp)
0x009bad3b: call Stub::jint_disjoint_arraycopy
; {runtime_call}
0x009bad40: add $0x18,%esp
0x009bad43: pop %ebp
0x009bad44: test %eax,0x940000 ; {poll_return}
0x009bad4a: ret
0x009bad4b: mov %edx,%edi
0x009bad4d: jmp 0x009bad51
0x009bad4f: mov %edx,%edi
0x009bad51: xor %edx,%edx
0x009bad53: mov %ebp,(%esp)
0x009bad56: xor %ebp,%ebp
0x009bad58: mov %ebp,0x4(%esp)
0x009bad5c: mov %edi,0x8(%esp)
0x009bad60: xchg %ax,%ax
0x009bad63: call 0x009baa40 ; OopMap{off=104}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy1@9 (line 7)
; {runtime_call}
0x009bad68: jmp 0x009bad40
0x009bad6a: test %edx,%edx
0x009bad6c: jge 0x009bad40
0x009bad6e: jmp 0x009bad51
0x009bad70: mov $0xfffffff6,%ecx
0x009bad75: mov %edx,0xc(%esp)
0x009bad79: xchg %ax,%ax
0x009bad7b: call 0x0099dd00 ; OopMap{ebp=Oop off=128}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy1@9 (line 7)
; {runtime_call}
0x009bad80: int3
0x009bad81: mov %ecx,%ebp
0x009bad83: mov %edx,0xc(%esp)
0x009bad87: mov $0xfffffff6,%ecx
0x009bad8c: xchg %ax,%ax
0x009bad8f: call 0x0099dd00 ; OopMap{ebp=Oop off=148}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy1@9 (line 7)
; {runtime_call}
0x009bad94: int3 ;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy1@9 (line 7)
0x009bad95: mov %eax,%ecx
0x009bad97: add $0x18,%esp
0x009bad9a: pop %ebp
0x009bad9b: jmp 0x009ba940 ; {runtime_call}
[Exception Handler]
[Stub Code]
0x009bada0: jmp 0x009b7500 ; {no_reloc}
[Deopt Handler Code]
0x009bada5: push $0x9bada5 ; {section_word}
0x009badaa: jmp 0x0099e280 ; {runtime_call}
0x009badaf: hlt
ArrayCopyTest.testArrayCopy2
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'testArrayCopy2' '(II)V' in 'ArrayCopyTest'
# parm0: ecx = int
# parm1: edx = int
# [sp+0x30] (sp of caller)
0x009b8b40: mov %eax,0xffffc000(%esp)
0x009b8b47: push %ebp
0x009b8b48: sub $0x28,%esp ;*synchronization entry
; - ArrayCopyTest::testArrayCopy2@-1 (line 12)
0x009b8b4b: mov %ecx,%ebx
0x009b8b4d: mov $0x3b812f8,%edi ; {oop(a 'java/lang/Class' = 'ArrayCopyTest')}
0x009b8b52: mov 0x70(%edi),%ecx ;*getstatic src
; - ArrayCopyTest::testArrayCopy2@0 (line 12)
0x009b8b55: mov 0x74(%edi),%eax ;*getstatic dest
; - ArrayCopyTest::testArrayCopy2@4 (line 12)
0x009b8b58: mov 0x8(%ecx),%ebp ; implicit exception: dispatches to 0x009b8bbc
0x009b8b5b: mov 0x8(%eax),%esi ; implicit exception: dispatches to 0x009b8bd1
0x009b8b5e: mov %ebx,%edi
0x009b8b60: test %ebx,%ebx
0x009b8b62: jl 0x009b8b96
0x009b8b64: add %edx,%ebx
0x009b8b66: cmp %ebx,%ebp
0x009b8b68: jb 0x009b8b9a
0x009b8b6a: cmp %edx,%esi
0x009b8b6c: jb 0x009b8b9e
0x009b8b6e: mov %edx,%esi
0x009b8b70: test %edx,%edx
0x009b8b72: jle 0x009b8bb6
0x009b8b74: lea 0xc(%ecx,%edi,4),%ebx
0x009b8b78: lea 0xc(%eax),%ecx
0x009b8b7b: mov %ebx,(%esp)
0x009b8b7e: mov %ecx,0x4(%esp)
0x009b8b82: mov %edx,0x8(%esp)
0x009b8b86: call Stub::jint_arraycopy ; {runtime_call}
0x009b8b8b: add $0x28,%esp
0x009b8b8e: pop %ebp
0x009b8b8f: test %eax,0x940000 ; {poll_return}
0x009b8b95: ret
0x009b8b96: mov %edx,%esi
0x009b8b98: jmp 0x009b8ba0
0x009b8b9a: mov %edx,%esi
0x009b8b9c: jmp 0x009b8ba0
0x009b8b9e: mov %edx,%esi
0x009b8ba0: mov %edi,%edx
0x009b8ba2: mov %eax,(%esp)
0x009b8ba5: xor %ebp,%ebp
0x009b8ba7: mov %ebp,0x4(%esp)
0x009b8bab: mov %esi,0x8(%esp)
0x009b8baf: call 0x009baa40 ; OopMap{off=116}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy2@9 (line 12)
; {runtime_call}
0x009b8bb4: jmp 0x009b8b8b
0x009b8bb6: test %edx,%edx
0x009b8bb8: jge 0x009b8b8b
0x009b8bba: jmp 0x009b8ba0
0x009b8bbc: mov $0xfffffff6,%ecx
0x009b8bc1: mov %ebx,%ebp
0x009b8bc3: mov %eax,0xc(%esp)
0x009b8bc7: mov %edx,0x10(%esp)
0x009b8bcb: call 0x0099dd00 ; OopMap{[12]=Oop off=144}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy2@9 (line 12)
; {runtime_call}
0x009b8bd0: int3
0x009b8bd1: mov %ecx,%ebp
0x009b8bd3: mov %ebx,0xc(%esp)
0x009b8bd7: mov %edx,0x10(%esp)
0x009b8bdb: mov $0xfffffff6,%ecx
0x009b8be0: xchg %ax,%ax
0x009b8be3: call 0x0099dd00 ; OopMap{ebp=Oop off=168}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy2@9 (line 12)
; {runtime_call}
0x009b8be8: int3 ;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy2@9 (line 12)
0x009b8be9: mov %eax,%ecx
0x009b8beb: add $0x28,%esp
0x009b8bee: pop %ebp
0x009b8bef: jmp 0x009ba940 ; {runtime_call}
0x009b8bf4: hlt
0x009b8bf5: hlt
0x009b8bf6: hlt
0x009b8bf7: hlt
0x009b8bf8: hlt
0x009b8bf9: hlt
0x009b8bfa: hlt
0x009b8bfb: hlt
0x009b8bfc: hlt
0x009b8bfd: hlt
0x009b8bfe: hlt
0x009b8bff: hlt
[Exception Handler]
[Stub Code]
0x009b8c00: jmp 0x009b7500 ; {no_reloc}
[Deopt Handler Code]
0x009b8c05: push $0x9b8c05 ; {section_word}
0x009b8c0a: jmp 0x0099e280 ; {runtime_call}
0x009b8c0f: hlt
ArrayCopyTest.testArrayCopy3
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'testArrayCopy3' '(II)V' in 'ArrayCopyTest'
# parm0: ecx = int
# parm1: edx = int
# [sp+0x30] (sp of caller)
0x009b8840: mov %eax,0xffffc000(%esp)
0x009b8847: push %ebp
0x009b8848: sub $0x28,%esp ;*synchronization entry
; - ArrayCopyTest::testArrayCopy3@-1 (line 17)
0x009b884b: mov %ecx,%ebx
0x009b884d: mov $0x3b812f8,%ebp ; {oop(a 'java/lang/Class' = 'ArrayCopyTest')}
0x009b8852: mov 0x70(%ebp),%ecx ;*getstatic src
; - ArrayCopyTest::testArrayCopy3@0 (line 17)
0x009b8855: mov 0x74(%ebp),%eax ;*getstatic dest
; - ArrayCopyTest::testArrayCopy3@4 (line 17)
0x009b8858: mov 0x8(%ecx),%ebp ; implicit exception: dispatches to 0x009b88bc
0x009b885b: mov 0x8(%eax),%edi ; implicit exception: dispatches to 0x009b88d1
0x009b885e: test %ebx,%ebx
0x009b8860: jl 0x009b8896
0x009b8862: cmp %edx,%ebp
0x009b8864: jb 0x009b889a
0x009b8866: mov %ebx,%esi
0x009b8868: add %edx,%esi
0x009b886a: cmp %esi,%edi
0x009b886c: jb 0x009b889e
0x009b886e: mov %edx,%edi
0x009b8870: test %edx,%edx
0x009b8872: jle 0x009b88b6
0x009b8874: lea 0xc(%eax,%ebx,4),%ebx
0x009b8878: lea 0xc(%ecx),%ebp
0x009b887b: mov %ebp,(%esp)
0x009b887e: mov %ebx,0x4(%esp)
0x009b8882: mov %edx,0x8(%esp)
0x009b8886: call Stub::jint_arraycopy ; {runtime_call}
0x009b888b: add $0x28,%esp
0x009b888e: pop %ebp
0x009b888f: test %eax,0x940000 ; {poll_return}
0x009b8895: ret
0x009b8896: mov %edx,%edi
0x009b8898: jmp 0x009b88a0
0x009b889a: mov %edx,%edi
0x009b889c: jmp 0x009b88a0
0x009b889e: mov %edx,%edi
0x009b88a0: xor %edx,%edx
0x009b88a2: mov %eax,(%esp)
0x009b88a5: mov %ebx,0x4(%esp)
0x009b88a9: mov %edi,0x8(%esp)
0x009b88ad: xchg %ax,%ax
0x009b88af: call 0x009baa40 ; OopMap{off=116}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy3@9 (line 17)
; {runtime_call}
0x009b88b4: jmp 0x009b888b
0x009b88b6: test %edx,%edx
0x009b88b8: jge 0x009b888b
0x009b88ba: jmp 0x009b88a0
0x009b88bc: mov $0xfffffff6,%ecx
0x009b88c1: mov %eax,%ebp
0x009b88c3: mov %ebx,0xc(%esp)
0x009b88c7: mov %edx,0x10(%esp)
0x009b88cb: call 0x0099dd00 ; OopMap{ebp=Oop off=144}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy3@9 (line 17)
; {runtime_call}
0x009b88d0: int3
0x009b88d1: mov %ecx,%ebp
0x009b88d3: mov %ebx,0xc(%esp)
0x009b88d7: mov %edx,0x10(%esp)
0x009b88db: mov $0xfffffff6,%ecx
0x009b88e0: xchg %ax,%ax
0x009b88e3: call 0x0099dd00 ; OopMap{ebp=Oop off=168}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy3@9 (line 17)
; {runtime_call}
0x009b88e8: int3 ;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy3@9 (line 17)
0x009b88e9: mov %eax,%ecx
0x009b88eb: add $0x28,%esp
0x009b88ee: pop %ebp
0x009b88ef: jmp 0x009ba940 ; {runtime_call}
0x009b88f4: hlt
0x009b88f5: hlt
0x009b88f6: hlt
0x009b88f7: hlt
0x009b88f8: hlt
0x009b88f9: hlt
0x009b88fa: hlt
0x009b88fb: hlt
0x009b88fc: hlt
0x009b88fd: hlt
0x009b88fe: hlt
0x009b88ff: hlt
[Exception Handler]
[Stub Code]
0x009b8900: jmp 0x009b7500 ; {no_reloc}
[Deopt Handler Code]
0x009b8905: push $0x9b8905 ; {section_word}
0x009b890a: jmp 0x0099e280 ; {runtime_call}
0x009b890f: hlt
ArrayCopyTest.testArrayCopy4
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'testArrayCopy4' '(II)V' in 'ArrayCopyTest'
# parm0: ecx = int
# parm1: edx = int
# [sp+0x20] (sp of caller)
0x009b85c0: mov %eax,0xffffc000(%esp)
0x009b85c7: push %ebp
0x009b85c8: sub $0x18,%esp ;*synchronization entry
; - ArrayCopyTest::testArrayCopy4@-1 (line 22)
0x009b85cb: mov $0x3b812f8,%ecx ; {oop(a 'java/lang/Class' = 'ArrayCopyTest')}
0x009b85d0: mov 0x70(%ecx),%ecx ;*getstatic src
; - ArrayCopyTest::testArrayCopy4@0 (line 22)
0x009b85d3: mov 0x8(%ecx),%ebx ; implicit exception: dispatches to 0x009b861c
0x009b85d6: cmp %edx,%ebx
0x009b85d8: jb 0x009b85fe
0x009b85da: mov %edx,%ebx
0x009b85dc: test %edx,%edx
0x009b85de: jle 0x009b8616
0x009b85e0: lea 0xc(%ecx),%ebp
0x009b85e3: mov %ebp,(%esp)
0x009b85e6: mov %ebp,0x4(%esp)
0x009b85ea: mov %edx,0x8(%esp)
0x009b85ee: call Stub::jint_disjoint_arraycopy
; {runtime_call}
0x009b85f3: add $0x18,%esp
0x009b85f6: pop %ebp
0x009b85f7: test %eax,0x940000 ; {poll_return}
0x009b85fd: ret
0x009b85fe: mov %edx,%ebx
0x009b8600: xor %edx,%edx
0x009b8602: mov %ecx,(%esp)
0x009b8605: xor %edi,%edi
0x009b8607: mov %edi,0x4(%esp)
0x009b860b: mov %ebx,0x8(%esp)
0x009b860f: call 0x009baa40 ; OopMap{off=84}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy4@9 (line 22)
; {runtime_call}
0x009b8614: jmp 0x009b85f3
0x009b8616: test %edx,%edx
0x009b8618: jge 0x009b85f3
0x009b861a: jmp 0x009b8600
0x009b861c: mov $0xfffffff6,%ecx
0x009b8621: mov %edx,%ebp
0x009b8623: call 0x0099dd00 ; OopMap{off=104}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy4@9 (line 22)
; {runtime_call}
0x009b8628: int3 ;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy4@9 (line 22)
0x009b8629: mov %eax,%ecx
0x009b862b: add $0x18,%esp
0x009b862e: pop %ebp
0x009b862f: jmp 0x009ba940 ; {runtime_call}
0x009b8634: hlt
0x009b8635: hlt
0x009b8636: hlt
0x009b8637: hlt
0x009b8638: hlt
0x009b8639: hlt
0x009b863a: hlt
0x009b863b: hlt
0x009b863c: hlt
0x009b863d: hlt
0x009b863e: hlt
0x009b863f: hlt
[Exception Handler]
[Stub Code]
0x009b8640: jmp 0x009b7500 ; {no_reloc}
[Deopt Handler Code]
0x009b8645: push $0x9b8645 ; {section_word}
0x009b864a: jmp 0x0099e280 ; {runtime_call}
0x009b864f: hlt
ArrayCopyTest.testArrayCopy5
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'testArrayCopy5' '(II)V' in 'ArrayCopyTest'
# parm0: ecx = int
# parm1: edx = int
# [sp+0x20] (sp of caller)
0x009b8340: mov %eax,0xffffc000(%esp)
0x009b8347: push %ebp
0x009b8348: sub $0x18,%esp ;*synchronization entry
; - ArrayCopyTest::testArrayCopy5@-1 (line 27)
0x009b834b: mov %ecx,%eax
0x009b834d: mov $0x3b812f8,%ecx ; {oop(a 'java/lang/Class' = 'ArrayCopyTest')}
0x009b8352: mov 0x70(%ecx),%edi ;*getstatic src
; - ArrayCopyTest::testArrayCopy5@0 (line 27)
0x009b8355: mov 0x8(%edi),%ebx ; implicit exception: dispatches to 0x009b83b8
0x009b8358: test %eax,%eax
0x009b835a: jl 0x009b8390
0x009b835c: mov %eax,%ebp
0x009b835e: add %edx,%ebp
0x009b8360: cmp %ebp,%ebx
0x009b8362: jb 0x009b8394
0x009b8364: cmp %edx,%ebx
0x009b8366: jb 0x009b8398
0x009b8368: mov %edx,%esi
0x009b836a: test %edx,%edx
0x009b836c: jle 0x009b83b2
0x009b836e: lea 0xc(%edi,%eax,4),%ecx
0x009b8372: lea 0xc(%edi),%ebx
0x009b8375: mov %ecx,(%esp)
0x009b8378: mov %ebx,0x4(%esp)
0x009b837c: mov %edx,0x8(%esp)
0x009b8380: call Stub::jint_arraycopy ; {runtime_call}
0x009b8385: add $0x18,%esp
0x009b8388: pop %ebp
0x009b8389: test %eax,0x940000 ; {poll_return}
0x009b838f: ret
0x009b8390: mov %edx,%esi
0x009b8392: jmp 0x009b839a
0x009b8394: mov %edx,%esi
0x009b8396: jmp 0x009b839a
0x009b8398: mov %edx,%esi
0x009b839a: mov %edi,%ecx
0x009b839c: mov %eax,%edx
0x009b839e: mov %edi,(%esp)
0x009b83a1: xor %ebp,%ebp
0x009b83a3: mov %ebp,0x4(%esp)
0x009b83a7: mov %esi,0x8(%esp)
0x009b83ab: call 0x009baa40 ; OopMap{off=112}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy5@9 (line 27)
; {runtime_call}
0x009b83b0: jmp 0x009b8385
0x009b83b2: test %edx,%edx
0x009b83b4: jge 0x009b8385
0x009b83b6: jmp 0x009b839a
0x009b83b8: mov $0xfffffff6,%ecx
0x009b83bd: mov %eax,%ebp
0x009b83bf: mov %edx,0xc(%esp)
0x009b83c3: call 0x0099dd00 ; OopMap{off=136}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy5@9 (line 27)
; {runtime_call}
0x009b83c8: int3 ;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy5@9 (line 27)
0x009b83c9: mov %eax,%ecx
0x009b83cb: add $0x18,%esp
0x009b83ce: pop %ebp
0x009b83cf: jmp 0x009ba940 ; {runtime_call}
0x009b83d4: hlt
0x009b83d5: hlt
0x009b83d6: hlt
0x009b83d7: hlt
0x009b83d8: hlt
0x009b83d9: hlt
0x009b83da: hlt
0x009b83db: hlt
0x009b83dc: hlt
0x009b83dd: hlt
0x009b83de: hlt
0x009b83df: hlt
[Exception Handler]
[Stub Code]
0x009b83e0: jmp 0x009b7500 ; {no_reloc}
[Deopt Handler Code]
0x009b83e5: push $0x9b83e5 ; {section_word}
0x009b83ea: jmp 0x0099e280 ; {runtime_call}
0x009b83ef: hlt
ArrayCopyTest.testArrayCopy6
[Entry Point]
[Verified Entry Point]
[Constants]
# {method} 'testArrayCopy6' '(II)V' in 'ArrayCopyTest'
# parm0: ecx = int
# parm1: edx = int
# [sp+0x20] (sp of caller)
0x009b80c0: mov %eax,0xffffc000(%esp)
0x009b80c7: push %ebp
0x009b80c8: sub $0x18,%esp ;*synchronization entry
; - ArrayCopyTest::testArrayCopy6@-1 (line 32)
0x009b80cb: mov %ecx,%ebp
0x009b80cd: mov $0x3b812f8,%ebx ; {oop(a 'java/lang/Class' = 'ArrayCopyTest')}
0x009b80d2: mov 0x70(%ebx),%eax ;*getstatic src
; - ArrayCopyTest::testArrayCopy6@0 (line 32)
0x009b80d5: mov 0x8(%eax),%ecx ; implicit exception: dispatches to 0x009b8138
0x009b80d8: mov %ebp,%ebx
0x009b80da: test %ebx,%ebx
0x009b80dc: jl 0x009b8110
0x009b80de: cmp %edx,%ecx
0x009b80e0: jb 0x009b8114
0x009b80e2: add %edx,%ebp
0x009b80e4: cmp %ebp,%ecx
0x009b80e6: jb 0x009b8118
0x009b80e8: mov %edx,%ebp
0x009b80ea: test %edx,%edx
0x009b80ec: jle 0x009b8132
0x009b80ee: lea 0xc(%eax,%ebx,4),%ebx
0x009b80f2: lea 0xc(%eax),%ecx
0x009b80f5: mov %ecx,(%esp)
0x009b80f8: mov %ebx,0x4(%esp)
0x009b80fc: mov %edx,0x8(%esp)
0x009b8100: call Stub::jint_arraycopy ; {runtime_call}
0x009b8105: add $0x18,%esp
0x009b8108: pop %ebp
0x009b8109: test %eax,0x940000 ; {poll_return}
0x009b810f: ret
0x009b8110: mov %edx,%ebp
0x009b8112: jmp 0x009b811a
0x009b8114: mov %edx,%ebp
0x009b8116: jmp 0x009b811a
0x009b8118: mov %edx,%ebp
0x009b811a: xor %edx,%edx
0x009b811c: mov %eax,%ecx
0x009b811e: mov %eax,(%esp)
0x009b8121: mov %ebx,0x4(%esp)
0x009b8125: mov %ebp,0x8(%esp)
0x009b8129: xchg %ax,%ax
0x009b812b: call 0x009baa40 ; OopMap{off=112}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy6@9 (line 32)
; {runtime_call}
0x009b8130: jmp 0x009b8105
0x009b8132: test %edx,%edx
0x009b8134: jge 0x009b8105
0x009b8136: jmp 0x009b811a
0x009b8138: mov $0xfffffff6,%ecx
0x009b813d: mov %edx,0xc(%esp)
0x009b8141: xchg %ax,%ax
0x009b8143: call 0x0099dd00 ; OopMap{off=136}
;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy6@9 (line 32)
; {runtime_call}
0x009b8148: int3 ;*invokestatic arraycopy
; - ArrayCopyTest::testArrayCopy6@9 (line 32)
0x009b8149: mov %eax,%ecx
0x009b814b: add $0x18,%esp
0x009b814e: pop %ebp
0x009b814f: jmp 0x009ba940 ; {runtime_call}
0x009b8154: hlt
0x009b8155: hlt
0x009b8156: hlt
0x009b8157: hlt
0x009b8158: hlt
0x009b8159: hlt
0x009b815a: hlt
0x009b815b: hlt
0x009b815c: hlt
0x009b815d: hlt
0x009b815e: hlt
0x009b815f: hlt
[Exception Handler]
[Stub Code]
0x009b8160: jmp 0x009b7500 ; {no_reloc}
[Deopt Handler Code]
0x009b8165: push $0x9b8165 ; {section_word}
0x009b816a: jmp 0x0099e280 ; {runtime_call}
0x009b816f: hlt
Uff…
6. Dvě varianty implementace metody System.arraycopy() při použití int[]
Výpis assembleru, uvedený v předchozí kapitole, si zajisté všichni podrobně prozkoumali :-), takže si všimli jedné významné odlišnosti – v některých metodách se při jejich překladu použilo volání call Stub::jint_arraycopy, kdežto v jiných metodách JIT překladač zvolil call Stub::jint_disjoint_arraycopy. Jak je to však možné, když bajtkód všech šesti metod byl prakticky stejný? JIT překladače se totiž při volbě, jak přesně volat funkci System.arraycopy(), dívají na konkrétní způsob volání a berou do úvahy především to, zda jsou prvky zarovnány (to u polí typu int[] na 32bitových platformách jsou) a zda a jak by mohlo dojít k překryvu kopírovaných polí. Důvod, proč tomu tak je, můžeme najít v JavaDocu:
If the src and dest arguments refer to the same array object, then the copying is performed as if the components at positions srcPos through srcPos+length-1 were first copied to a temporary array with length components and then the contents of the temporary array were copied into positions destPos through destPos+length-1 of the destination array.
JavaDoc samozřejmě neříká, že se kopie skutečně provede přes další pole, to by nebylo příliš vhodné. Kopie je provedena in-situ, ovšem jiným způsobem – může se například provádět kopie od posledního prvku a nikoli od prvku prvního atd.
Volání |
JIT překladač volá |
System.arraycopy(src, 0, dest, 0, length) |
Stub::jint_disjoint_arraycopy |
System.arraycopy(src, offset, dest, 0, length) |
Stub::jint_arraycopy |
System.arraycopy(src, 0, dest, offset, length) |
Stub::jint_arraycopy |
System.arraycopy(src, 0, src, 0, length) |
Stub::jint_disjoint_arraycopy |
System.arraycopy(src, offset, src, 0, length) |
Stub::jint_arraycopy |
System.arraycopy(src, 0, src, offset, length) |
Stub::jint_arraycopy |
V tomto případě JIT překladač rozhodl, že když jsou oba offsety stejné (konkrétně nulové, ale může se jednat i o jinou hodnotu), může se volat optimalizovaná funkce jint_disjoint_arraycopy, protože je zaručeno, že se při kopii budou prvky buď kopírovat do odlišného pole (tedy bez překryvu), popř. se bude jednat o stejné pole a prvky se budou kopírovat samy na sebe. Stejnou funkci by JIT překladač volal i v případě, že by se kopírovaly prvky stejného pole a současně by platilo dest_offset<src_offset. Ovšem jak vidíme, tuto optimalizaci JIT překladač nezvolil v žádném případě, protože jsme nepoužili konstanty.
Poučení pro praxi:
-
System.arraycopy dokáže kopírovat prvky v jednom poli.
-
System.arraycopy dokáže kopírovat prvky v jednom poli i pozpátku (viz například rozdíl mezi memcpy() a memmove() v céčku).
- Pokud je to možné, používejte pro offsety konstanty.
- V případě, že JIT zvolí volání jint_disjoint_arraycopy, lze předpokládat nepatrně vyšší výkon (viz další kapitoly).
7. Benchmark ArrayCopyTest2.java
Vzhledem k tomu, že se JIT překladač evidentně snaží při běhu aplikace rozlišovat mezi voláním jint_arraycopy a jint_disjoint_arraycopy, mělo by to (alespoň teoreticky) znamenat, že jint_disjoint_arraycopy bude při použití v aplikaci rychlejší. Zkusme si tedy tento předpoklad ověřit, a to na benchmarku nazvaném ArrayCopyTest2, který je založený na výše popsaném demonstračním příkladu ArrayCopyTest. Při spuštění tohoto benchmarku se nejprve provede několik volání testovaných metod bez měření (ovšem právě v této chvíli dojde k JIT překladu) a posléze se spustí samotné měření:
/**
* Benchmark pro zjisteni intrinsic "funkci" implementujicich
* @link System#arraycopy(java.lang.Object, int, java.lang.Object, int, int).
* V tomto benchmarku se provadi kopie prvku typu int.
*
* @author Pavel Tisnovsky
*/
public class ArrayCopyTest2 {
private static final int ARRAYS_LENGTH = 50000;
private static final int WARMUP_ITERS = 20000;
private static final int BENCHMARK_ITERS = 20000;
/** Pole pouzivana pro kopii prvku v benchmarku */
static int[] src = new int[ARRAYS_LENGTH];
static int[] dest = new int[ARRAYS_LENGTH];
/** Kopie mezi rozdilnymi poli, oba offsety jsou nulove */
public static void testArrayCopy1(int offset, int length) {
System.arraycopy(src, 0, dest, 0, length);
}
/** Kopie mezi rozdilnymi poli, nulovy je jen druhy offset */
public static void testArrayCopy2(int offset, int length) {
System.arraycopy(src, offset, dest, 0, length);
}
/** Kopie mezi rozdilnymi poli, nulovy je jen prvni offset */
public static void testArrayCopy3(int offset, int length) {
System.arraycopy(src, 0, dest, offset, length);
}
/** Kopie prvku v jednom poli, oba offsety jsou nulove */
public static void testArrayCopy4(int offset, int length) {
System.arraycopy(src, 0, src, 0, length);
}
/** Kopie prvku v jednom poli, nulovy je jen druhy offset */
public static void testArrayCopy5(int offset, int length) {
System.arraycopy(src, offset, src, 0, length);
}
/** Kopie prvku v jednom poli, nulovy je jen prvni offset */
public static void testArrayCopy6(int offset, int length) {
System.arraycopy(src, 0, src, offset, length);
}
/**
* Spusteni benchmarku.
*/
private static void runSimpleBenchmark() {
warmup();
benchmark();
}
/**
* Zahrivaci faze benchmarku.
*/
private static void warmup() {
System.out.println("Warmup phase...");
// donutime JIT k prekladu, soucasne se vsak neprekroci
// meze poli
for (int j = 0; j < 10; j++) {
System.out.print(j);
System.out.print(' ');
for (int i = 0; i < WARMUP_ITERS; i++) {
testArrayCopy1(i, i);
testArrayCopy2(i, i);
testArrayCopy3(i, i);
testArrayCopy4(i, i);
testArrayCopy5(i, i);
testArrayCopy6(i, i);
}
}
System.out.println(" done");
}
/**
* Vlastni benchmark.
*/
private static void benchmark() {
long t1, t2, delta_t;
for (int testNo = 1; testNo <= 6; testNo++) {
// provest test a zmerit cas behu testu
t1 = System.nanoTime();
for (int i = 0; i < BENCHMARK_ITERS; i++) {
// skarede, ale zde nelze pouzit reflexi
// (stale cekame na moznost pouziti referenci na metody!)
switch (testNo) {
case 1: testArrayCopy1(i, i); break;
case 2: testArrayCopy2(i, i); break;
case 3: testArrayCopy3(i, i); break;
case 4: testArrayCopy4(i, i); break;
case 5: testArrayCopy5(i, i); break;
case 6: testArrayCopy6(i, i); break;
}
}
t2 = System.nanoTime();
delta_t = t2 - t1;
// vypis casu pro jeden test
System.out.format("Method ArrayCopyTest2.testArrayCopy%d time: %,12d ns\n", testNo, delta_t);
}
}
public static void main(String[] args) {
runSimpleBenchmark();
}
}
// finito
8. Výsledky běhu benchmarku
Podívejme se nyní, jaké výsledky dostaneme na reálném počítači s 32bitovým mikroprocesorem architektury i386:
Warmup phase...
0 1 2 3 4 5 6 7 8 9 done
Method ArrayCopyTest.testArrayCopy1 time: 442 289 732 ns
Method ArrayCopyTest.testArrayCopy2 time: 471 646 536 ns
Method ArrayCopyTest.testArrayCopy3 time: 470 064 492 ns
Method ArrayCopyTest.testArrayCopy4 time: 295 876 432 ns
Method ArrayCopyTest.testArrayCopy5 time: 468 205 318 ns
Method ArrayCopyTest.testArrayCopy6 time: 460 168 822 ns
Výsledky si můžeme ukázat i na grafu:
Výsledky benchmarku jsou skutečně zajímavé. Ukazuje se na nich především to, že jint_disjoint_arraycopy je skutečně rychlejší než jint_arraycopy, ovšem míra urychlení je rozdílná podle toho, zda jsou kopírovány prvky z jednoho pole do pole jiného (zde je dosaženo urychlení přibližně o 6 %, což není vůbec špatné), či zda je prováděna kopie v rámci jediného pole (zde vlastně jint_disjoint_arraycopy nemusí provést žádnou operaci).
9. Volby UseXMMForArrayCopy a UseUnalignedLoadStores a jejich vliv na System.arraycopy
Konkrétní způsob implementace funkcí jint_disjoint_arraycopy a jint_arraycopy závisí na dalších dvou volbách nazvaných UseXMMForArrayCopy a UseUnalignedLoadStores. Pokud je použita první volba, budou se při kopii využívat registry SSE2, což by teoreticky mělo vést k vyššímu výkonu v případě, že jsou prvky zarovnány na osm bajtů (pokud se nepoužijí registry SSE2, mohou se použít registry MMX, o čem je rozhodnuto automaticky při startu JVM). Další volba povoluje či zakazuje použití instrukce MOVDQU namísto instrukce MOVQ při čtení a zápisu prvků, které nejsou v paměti zarovnány.
Obě volby jsou v základním nastavení Hotspotu zakázány, pojďme si tedy vyzkoušet, zda dojde k nějaké změně v chování benchmarku v případě, že obě volby budeme zapínat a vypínat:
java -server -XX:CompileThreshold=10000 -XX:-UseXMMForArrayCopy -XX:-UseUnalignedLoadStores ArrayCopyTest2
Warmup phase...
0 1 2 3 4 5 6 7 8 9 done
Method ArrayCopyTest.testArrayCopy1 time: 443 521 732 ns
Method ArrayCopyTest.testArrayCopy2 time: 474 578 752 ns
Method ArrayCopyTest.testArrayCopy3 time: 445 409 682 ns
Method ArrayCopyTest.testArrayCopy4 time: 286 016 520 ns
Method ArrayCopyTest.testArrayCopy5 time: 447 544 590 ns
Method ArrayCopyTest.testArrayCopy6 time: 447 518 888 ns
java -server -XX:CompileThreshold=10000 -XX:+UseXMMForArrayCopy -XX:-UseUnalignedLoadStores ArrayCopyTest2
Warmup phase...
0 1 2 3 4 5 6 7 8 9 done
Method ArrayCopyTest.testArrayCopy1 time: 446 858 748 ns
Method ArrayCopyTest.testArrayCopy2 time: 473 852 124 ns
Method ArrayCopyTest.testArrayCopy3 time: 449 020 478 ns
Method ArrayCopyTest.testArrayCopy4 time: 307 197 424 ns
Method ArrayCopyTest.testArrayCopy5 time: 448 431 014 ns
Method ArrayCopyTest.testArrayCopy6 time: 449 531 714 ns
java -server -XX:CompileThreshold=10000 -XX:-UseXMMForArrayCopy -XX:+UseUnalignedLoadStores ArrayCopyTest2
Warmup phase...
0 1 2 3 4 5 6 7 8 9 done
Method ArrayCopyTest.testArrayCopy1 time: 577 163 018 ns
Method ArrayCopyTest.testArrayCopy2 time: 537 454 342 ns
Method ArrayCopyTest.testArrayCopy3 time: 526 903 558 ns
Method ArrayCopyTest.testArrayCopy4 time: 691 708 862 ns
Method ArrayCopyTest.testArrayCopy5 time: 515 320 520 ns
Method ArrayCopyTest.testArrayCopy6 time: 529 278 162 ns
java -server -XX:CompileThreshold=10000 -XX:+UseXMMForArrayCopy -XX:+UseUnalignedLoadStores ArrayCopyTest2
Warmup phase...
0 1 2 3 4 5 6 7 8 9 done
Method ArrayCopyTest.testArrayCopy1 time: 629 873 070 ns
Method ArrayCopyTest.testArrayCopy2 time: 600 535 264 ns
Method ArrayCopyTest.testArrayCopy3 time: 620 208 432 ns
Method ArrayCopyTest.testArrayCopy4 time: 997 168 636 ns
Method ArrayCopyTest.testArrayCopy5 time: 577 411 376 ns
Method ArrayCopyTest.testArrayCopy6 time: 620 854 326 ns
Výsledky je nejlepší si porovnat na grafu:

Můžeme vidět, že na testovaném mikroprocesoru nedošlo při použití obou voleb ke zlepšení, ale naopak ke zhoršení, což ovšem nemusí platit pro všechny případy! V praxi je tedy dobré vědět, že tyto volby existují a že je v případě potřeby možné je zapnout, ovšem až poté, co profiler ukáže skutečný nárůst výkonu aplikace.
P.S.: Bylo by jistě zajímavé vidět, jak se tyto volby budou chovat na starších, popř. naopak na nejmodernějších mikroprocesorech, využití SSE2 bude pravděpodobně výhodnější pouze na novějších mikroprocesorech firmy Intel, nicméně praxe bývá od teoretických závěrů mnohdy značně vzdálená.
10. Obsah následující části seriálu
Při implementaci a především při optimalizaci funkce System.arraycopy() se museli programátoři potýkat ještě s dalšími dvěma problémy. První z nich se týká kopií prvků v případě, že celkový počet přenesených bajtů není zarovnán na hodnotu 4 či 8. Druhý problém spočívá v kopiích prvků, které již od začátku nejsou zarovnány. Tento problém nastává v praxi poměrně často, neboť si musíme uvědomit, že System.arraycopy() lze použít i pro pole typu byte[], short[] či char[], zatímco moderní mikroprocesory mají 32bitové či 64bitové sběrnice upravené pro přenos dat zarovnaných na násobky 4, 8 či někdy i 16 bajtů, což u výše zmíněných polí nelze zaručit (přesněji řečeno, samotný začátek pole je zarovnán správně, ovšem offset může být nastaven například na liché číslo). Touto problematikou i způsoby jejího řešení se budeme zabývat příště.
11. Repositář se všemi testovacími příklady
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ů:
12. Odkazy na Internetu
- 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