Pohled pod kapotu JVM – základy optimalizace aplikací naprogramovaných v Javě (záhadná funkce System.arraycopy)

22. 10. 2013
Doba čtení: 33 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji Javy si řekneme, jakým způsobem je v Hotspotu implementována velmi důležitá a často využívaná funkce System.arraycopy(). Uvidíme, že konkrétní tvar této funkce se bude lišit v závislosti na zarovnání či na překryvu kopírovaných prvků polí.

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 UseXMMForArrayCopyUseUnalignedLoadStores 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()testArrayCopy3() shodné, stejně jako budou identické bajtkódy metod testArrayCopy4()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:

  1. System.arraycopy dokáže kopírovat prvky v jednom poli.
  2. System.arraycopy dokáže kopírovat prvky v jednom poli i pozpátku (viz například rozdíl mezi memcpy()memmove() v céčku).
  3. Pokud je to možné, používejte pro offsety konstanty.
  4. 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_arraycopyjint_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 UseXMMForArrayCopyUseUnalignedLoadStores a jejich vliv na System.arraycopy

Konkrétní způsob implementace funkcí jint_disjoint_arraycopyjint_arraycopy závisí na dalších dvou volbách nazvaných UseXMMForArrayCopyUseUnalignedLoadStores. 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á.

bitcoin školení listopad 24

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

  1. GC safe-point (or safepoint) and safe-region
    http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html
  2. Safepoints in HotSpot JVM
    http://blog.ragozin.info/2012/10/sa­fepoints-in-hotspot-jvm.html
  3. Java theory and practice: Synchronization optimizations in Mustang
    http://www.ibm.com/develo­perworks/java/library/j-jtp10185/
  4. How to build hsdis
    http://hg.openjdk.java.net/jdk7/hot­spot/hotspot/file/tip/src/sha­re/tools/hsdis/README
  5. Java SE 6 Performance White Paper
    http://www.oracle.com/technet­work/java/6-performance-137236.html
  6. Lukas Stadler's Blog
    http://classparser.blogspot­.cz/2010/03/hsdis-i386dll.html
  7. How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
    http://dropzone.nfshost.com/hsdis.htm
  8. PrintAssembly
    https://wikis.oracle.com/dis­play/HotSpotInternals/Prin­tAssembly
  9. The Java Virtual Machine Specification: 3.14. Synchronization
    http://docs.oracle.com/ja­vase/specs/jvms/se7/html/jvms-3.html#jvms-3.14
  10. The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4
  11. The Java Virtual Machine Specification: 17.4. Memory Model
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.4
  12. The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
    http://docs.oracle.com/ja­vase/specs/jls/se7/html/jls-17.html#jls-17.7
  13. Open Source ByteCode Libraries in Java
    http://java-source.net/open-source/bytecode-libraries
  14. ASM Home page
    http://asm.ow2.org/
  15. Seznam nástrojů využívajících projekt ASM
    http://asm.ow2.org/users.html
  16. ObjectWeb ASM (Wikipedia)
    http://en.wikipedia.org/wi­ki/ObjectWeb_ASM
  17. Java Bytecode BCEL vs ASM
    http://james.onegoodcooki­e.com/2005/10/26/java-bytecode-bcel-vs-asm/
  18. BCEL Home page
    http://commons.apache.org/bcel/
  19. Byte Code Engineering Library (před verzí 5.0)
    http://bcel.sourceforge.net/
  20. Byte Code Engineering Library (verze >= 5.0)
    http://commons.apache.org/pro­per/commons-bcel/
  21. BCEL Manual
    http://commons.apache.org/bcel/ma­nual.html
  22. Byte Code Engineering Library (Wikipedia)
    http://en.wikipedia.org/wiki/BCEL
  23. BCEL Tutorial
    http://www.smfsupport.com/sup­port/java/bcel-tutorial!/
  24. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
  25. Bytecode Outline plugin for Eclipse (screenshoty + info)
    http://asm.ow2.org/eclipse/index.html
  26. Javassist
    http://www.jboss.org/javassist/
  27. Byteman
    http://www.jboss.org/byteman
  28. Java programming dynamics, Part 7: Bytecode engineering with BCEL
    http://www.ibm.com/develo­perworks/java/library/j-dyn0414/
  29. The JavaTM Virtual Machine Specification, Second Edition
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/VMSpec­TOC.doc.html
  30. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  31. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  32. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  33. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  34. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  35. aspectj (Eclipse)
    http://www.eclipse.org/aspectj/
  36. Aspect-oriented programming (Wikipedia)
    http://en.wikipedia.org/wi­ki/Aspect_oriented_program­ming
  37. AspectJ (Wikipedia)
    http://en.wikipedia.org/wiki/AspectJ
  38. EMMA: a free Java code coverage tool
    http://emma.sourceforge.net/
  39. Cobertura
    http://cobertura.sourceforge.net/
  40. jclasslib bytecode viewer
    http://www.ej-technologies.com/products/jclas­slib/overview.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.