Hlavní navigace

Pohled pod kapotu JVM – základy optimalizace aplikací naprogramovaných v Javě (5)

15. 10. 2013
Doba čtení: 23 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Java si popíšeme některé další typy optimalizací prováděných automaticky JIT překladači. Zaměříme se jak na známé optimalizační metody typu rozbalení smyček či eliminaci volání metod, tak i na využití již připravených sekvencí instrukcí (intrinsic funkce).

Obsah

1. Pohled pod kapotu JVM – základy optimalizace aplikací naprogramovaných v Javě (5)

2. Alokace pracovních registrů

3. Rozbalení programových smyček: loop unrolling

4. První demonstrační příklad – rozbalení smyček

5. Method inlining

6. Druhý demonstrační příklad – eliminace volání metod

7. Použití intrinsic „funkcí“

8. Seznam všech „intrinsic“

9. Metoda System.arraycopy()

10. Repositář se zdrojovými kódy všech demonstračních i testovacích příkladů

11. Odkazy na Internetu

1. Pohled pod kapotu JVM – základy optimalizace aplikací naprogramovaných v Javě (5)

V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji Javy si stručně popíšeme několik optimalizačních metod prováděných JIT překladači typu client a především pak JIT překladači typu server. Účinek dále popsaných optimalizačních metod si ukážeme na několika demonstračních příkladech. Před popisem jednotlivých optimalizačních metod si však nejprve řekněme, na jaké oblasti se vlastně JIT překladače zaměřují:

  1. Největším problémem, který se JIT překladače snaží (někdy více, někdy ale již méně úspěšně :-) eliminovat, jsou přístupy do operační paměti. Každý přístup do operační paměti může znamenat nutnost synchronizace vyrovnávacích pamětí (cache), což může způsobit vážné snížení výkonnosti aplikací zejména na systémech s mnoha jádry. Právě zde se uplatňuje Amdahlův zákon (vztah).
  2. Dalším problémem je volání metod, které je taktéž vhodné eliminovat. Překladače se snaží zjistit, které metody jsou natolik jednoduché a přitom často volané, aby se namísto jejich skutečného volání mohlo nakopírovat tělo metody přímo do volajícího kódu. Tato optimalizace – zde založená na reálných datech získaných při běhu aplikace! – se nazývá method inlining.
  3. Třetí problém způsobuje používání zámků. I zde se překladače snaží různým způsobem buď zcela použití zámků eliminovat (někdy to skutečně možné je), popř. použít jeden a týž zámek pro delší úsek kódu.
  4. Na mnoha architekturách je taktéž vhodné eliminovat podmíněné a nepodmíněné skoky, popř. přerovnat kód takovým způsobem, aby prediktory skoků dokázaly správně odhadnout, zda se podmíněný skok provede či nikoli. Základní optimalizační metodou je zde rozbalování smyček (loop unrolling).
  5. Některé metody jsou tak často používané a současně tak obecné, že se vyplatí dopředu si připravit optimalizovanou sekvenci strojových instrukcí implementujících tyto metody na cílové platformě. Klasickým příkladem je metoda System.arraycopy(), jejíž kód je již připraven, a to hned v několika variantách optimalizovaných pro různé případy, které mohou v praxi nastat (překryv polí, zarovnání či naopak nezarovnání polí atd.).

2. Alokace pracovních registrů

JIT překladače typu client i server jsou v současnosti implementovány na mikroprocesorových architekturách založených na pracovních registrech a nikoli například na zásobníku operandů (tyto alternativní architektury jsou dnes spíše minoritní). To mj. znamená, že JIT překladač musí vhodným způsobem transformovat původní bajtkód založený na použití zásobníku operandů na strojový kód, v němž se používají pracovní registry cílové mikroprocesorové architektury (i686, x86_64, SPARC, ARM, AArch64…). Počet dostupných pracovních registrů i způsob jejich využití je na každé mikroprocesorové architektuře odlišný, což je jeden z důvodů, proč se JIT překladače musí pro každou novou architekturu vždy z určité části přepsat; to je mimochodem poměrně zajímavé téma, kterému se ještě v tomto seriálu budeme podrobněji věnovat, zejména s ohledem na právě vznikající C1 a C2 JIT překladače určené pro mikroprocesory AArch64.

Pro alokaci registrů se využívají dva odlišné způsoby. JIT překladač typu client je – jak již víme z předchozích částí tohoto seriálu – zaměřen především na rychlost překladu a nikoli na kvalitu výsledného kódu, a proto používá jednoduchou lineární alokaci registrů souběžně s takzvanými šablonami kódu (code templates), v nichž se pro implementaci různých operací používají vždy stejné registry. Naproti tomu JIT překladač typu server již při alokaci registrů vytváří graf dostupnosti a snaží se registry využít optimálním způsobem, zejména s ohledem na to, aby se mezivýsledky různých operací nemusely ukládat do paměti a aby přesuny dat mezi registry byly minimalizovány. Na většině současných architektur je navíc alokace rozdělena minimálně na dvě části – registry pro ALU operace a adresování a registry pro provádění operací s čísly s plovoucí řádovou čárkou (floating point). K této problematice se taktéž ještě vrátíme.

3. Rozbalení programových smyček: loop unrolling

Jednou ze základních optimalizací, o níž jsme se zmiňovali v první kapitole, je rozbalení programových smyček neboli loop unrolling. Překladače se k této optimalizaci uchylují především z toho důvodu, že moderní mikroprocesory obsahují dlouhé pipeline. Při výskytu skoku se musí vyřešit problém, co se má udělat s instrukcemi, které se nacházejí v rozpracovaném stavu uvnitř pipeline. Většinou nastává nejhorší případ, kdy se tyto instrukce musí zahodit a po provedení skoku se musí pipeline znovu naplnit, tentokrát jinými instrukcemi (zde trošku celý problém zjednodušuji). To má samozřejmě za následek snížení výpočetního výkonu, které může být někdy velmi radikální (například Pentium 4 má pipeline rozdělenou na více než 30 řezů, tj. může se v ní nacházet třicet rozpracovaných instrukcí). Je tedy vhodné nějakým způsobem skoky co nejvíce omezit či snížit jejich negativní dopad na rychlost běhu programu. První metody, tj. omezení skoků, jsou vesměs záležitostí překladače či ruční optimalizace kódu. A právě sem spadá i zmíněné rozbalení smyček.

Většinou se však neprovádí jen jednoduché rozbalení smyčky ve stylu, že by se například sekvence instrukcí pro tři po sobě jdoucí iterace jednoduše nakopírovaly za sebe, ale JIT překladač se současně snaží i o eliminaci zbytečných přesunů dat mezi operační pamětí a pracovními registry. To je jeden z důvodů, proč je při pohledu na disassemblovaný kód někdy velmi těžké určit, jak přesně se rozbalení smyčky provedlo. Navíc se ukazuje, že v některých případech by schopný programátor dokázal výsledný strojový kód optimalizovat ještě lépe než JIT překladač (ten je totiž omezen mj. i safepointy, které většinou při optimalizacích nepřekračuje), ovšem to je fakt, který lze aplikovat i například na klasické překladače programovacích jazyků typu C, C++ či Fortran.

4. První demonstrační příklad – rozbalení smyček

Ukažme si nyní způsob rozbalení smyčky u demonstračního programu pro výpočet faktoriálu. Faktoriál je vypočten v metodě factorial, v níž je výpočet realizován jednoduchou počítanou programovou smyčkou. Pro jednoduchost jsou výpočty prováděny s výsledkem typu int, protože se budeme dívat na disassemblovaný kód pro platformu x86, kde by výpočty s 64bitovými čísly byly poměrně kryptické (kombinace instrukcí mulimul atd.). Zdrojový kód demonstračního příkladu vypadá následovně:

public class Factorial {
 
    public static int factorial(int n) {
        if (n <= 1) return 1;
        int result = 1;
        for (int i = 1; i <= n; i++) {
            result *= i;
        }
        return result;
    }
 
    public static void testFactorial() {
        for (int i = 0; i < 16; i++) {
            System.out.println(i + "! = " + factorial(i));
        }
    }
 
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            testFactorial();
        }
    }
}

Metoda factorial se přeloží do následující sekvence instrukcí bajtkódu. Vlastní programová smyčka je od ostatního kódu pro větší přehlednost oddělena:

  public static int factorial(int);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: iload_0
         1: iconst_1
         2: if_icmpgt     7
         5: iconst_1
         6: ireturn
         7: iconst_1
         8: istore_1
         9: iconst_1
        10: istore_2
        =================================
        11: iload_2
        12: iload_0
        13: if_icmpgt     26
        16: iload_1
        17: iload_2
        18: imul
        19: istore_1
        20: iinc          2, 1
        23: goto          11
        =================================
        26: iload_1
        27: ireturn
      LineNumberTable:
        line 4: 0
        line 5: 7
        line 6: 9
        line 7: 16
        line 6: 20
        line 9: 26
      StackMapTable: number_of_entries = 3
           frame_type = 7 /* same */
           frame_type = 253 /* append */
             offset_delta = 3
        locals = [ int, int ]
           frame_type = 250 /* chop */
          offset_delta = 14

A zde se již můžeme podívat na výsledný (disassemblovaný) strojový kód, kde již došlo k rozbalení počítané programové smyčky. Povšimněte si, jakým způsobem nám disassembler oznamuje mapování strojových instrukcí na instrukce bajtkódu (poznámky obsahující jméno metody s indexem za zavináčem):

Decoding compiled method 0x009bab88:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
[Exception Handler]
[Stub Code]
  0x009bad40: jmp    0x009b7240         ;   {no_reloc}
[Deopt Handler Code]
  0x009bad45: push   $0x9bad45          ;   {section_word}
  0x009bad4a: jmp    0x0099e280         ;   {runtime_call}
  0x009bad4f: hlt
  # {method} 'factorial' '(I)I' in 'Factorial'
  # parm0:    ecx       = int
  #           [sp+0x10]  (sp of caller)
  0x009bac80: mov    %eax,0xffffc000(%esp)
  0x009bac87: push   %ebp
  0x009bac88: sub    $0x8,%esp          ;*synchronization entry
                                        ; - Factorial::factorial@-1 (line 4)
  0x009bac8b: mov    $0x1,%eax
  0x009bac90: cmp    $0x1,%ecx
  0x009bac93: jle    0x009bad0a         ;*if_icmpgt
                                        ; - Factorial::factorial@2 (line 4)
  0x009bac95: cmp    $0x1,%ecx
  0x009bac98: jl     0x009bad0a         ;*if_icmpgt
                                        ; - Factorial::factorial@13 (line 6)
  0x009bac9a: cmp    $0x7ffffffe,%ecx
  0x009baca0: jg     0x009bad15         ;*iload_1
                                        ; - Factorial::factorial@16 (line 7)
  0x009baca2: mov    %ecx,%edi
  0x009baca4: inc    %edi
  0x009baca5: add    $0xfffffffe,%ecx
  0x009baca8: mov    $0x80000000,%ebx
  0x009bacad: cmp    %ecx,%edi
  0x009bacaf: cmovl  %ebx,%ecx
  0x009bacb2: mov    $0x2,%edx
  0x009bacb7: cmp    $0x2,%ecx
  0x009bacba: jle    0x009bad25
  0x009bacbc: mov    $0x2,%eax
  0x009bacc1: jmp    0x009bace2
  0x009bacc3: mov    %esi,%ecx
  0x009bacc5: imul   %eax,%ecx
  0x009bacc8: mov    %ecx,%eax          ;*imul
                                        ; - Factorial::factorial@18 (line 7)
  0x009bacca: inc    %esi               ;*iinc
                                        ; - Factorial::factorial@20 (line 6)
  0x009baccb: cmp    %edi,%esi
  0x009baccd: jl     0x009bacc3         ;*if_icmpgt
                                        ; - Factorial::factorial@13 (line 6)
  0x009baccf: jmp    0x009bad0a
  0x009bacd1: nopw   0x0(%eax,%eax,1)
  0x009bacdc: xchg   %ax,%ax
  0x009bace0: mov    %esi,%edx          ;*imul
                                        ; - Factorial::factorial@18 (line 7)
  0x009bace2: mov    %edx,%ebx
  0x009bace4: add    $0x3,%ebx
  0x009bace7: mov    %edx,%esi
  0x009bace9: add    $0x4,%esi          ;*iinc
                                        ; - Factorial::factorial@20 (line 6)
  0x009bacec: mov    %edx,%ebp
  0x009bacee: add    $0x2,%ebp
  0x009bacf1: inc    %edx
  0x009bacf2: imul   %eax,%edx
  0x009bacf5: imul   %ebp,%edx
  0x009bacf8: imul   %edx,%ebx
  0x009bacfb: mov    %esi,%eax
  0x009bacfd: imul   %ebx,%eax          ;*imul
                                        ; - Factorial::factorial@18 (line 7)
  0x009bad00: cmp    %ecx,%esi
  0x009bad02: jl     0x009bace0         ;*if_icmpgt
                                        ; - Factorial::factorial@13 (line 6)
  0x009bad04: cmp    %edi,%esi
  0x009bad06: jl     0x009bacca
  0x009bad08: mov    %ebx,%eax
  0x009bad0a: add    $0x8,%esp
  0x009bad0d: pop    %ebp
  0x009bad0e: test   %eax,0x940000      ;   {poll_return}
  0x009bad14: ret
  0x009bad15: mov    %ecx,%ebp
  0x009bad17: mov    $0xffffff7e,%ecx
  0x009bad1c: xchg   %ax,%ax
  0x009bad1f: call   0x0099dd00         ; OopMap{off=164}
                                        ;*iload_1
                                        ; - Factorial::factorial@16 (line 7)
                                        ;   {runtime_call}
  0x009bad24: int3                      ;*iload_1
                                        ; - Factorial::factorial@16 (line 7)
  0x009bad25: mov    $0x2,%esi
  0x009bad2a: mov    $0x2,%eax
  0x009bad2f: mov    $0x1,%ebx
  0x009bad34: jmp    0x009bad04         ;*if_icmpgt
                                        ; - Factorial::factorial@13 (line 6)
  0x009bad36: hlt
  0x009bad37: hlt
  0x009bad38: hlt
  0x009bad39: hlt
  0x009bad3a: hlt
  0x009bad3b: hlt
  0x009bad3c: hlt
  0x009bad3d: hlt
  0x009bad3e: hlt
  0x009bad3f: hlt

5. Method inlining

Další optimalizační metodou, o níž jsme se zmínili v první kapitole, je eliminace volání metod neboli method inlining. Tato optimalizační metoda je založena na známém faktu, že volání metod/funkcí s krátkým (přesněji řečeno jednoduchým) tělem je neekonomické, a to z důvodu nutnosti přípravy parametrů pro volanou funkci i kvůli vlastní režii volání (strojové instrukce typu callreturn, popř. na některých architekturách instrukce typu BL=branch+link). JIT překladač tedy dokáže detekovat metody, jejichž volání by bylo vhodné nahradit vložením těla metody přímo do volající metody. Jedním z parametrů virtuálního stroje, který ovlivňuje rozhodování o použití této optimalizační metody, je parametr nazvaný InlineSmallCode, který je v případě architektury x86 nastaven na hodnotu 1000 (velikost kódu) a parametr MinInliningThreshold určující, kolikrát musí být metoda volána v čase běhu aplikace.

Základní informaci o tom, které metody jsou touto optimalizační technikou nahrazeny svým tělem, je možné získat následujícím způsobem:

java -server -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining FooBar

Podívejme se na příklad použití těchto dvou přepínačů ve chvíli, kdy jsou použity při spouštění virtuálního stroje s demonstračním benchmarkem http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/file/05d05b5fd08d/jit/Man­delbrotRenderer.java, který jsme si popsali již v devadesáté sedmé části tohoto seriálu:

Informace o method inliningu:

java -server -XX:CompileThreshold=10000 -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining MandelbrotRenderer
                            @ 17   sun.java2d.StateTrackableDelegate::markDirty (6 bytes)   inline (hot)
                            @ 59   MandelbrotRenderer::iteration (93 bytes)   inline (hot)
                            @ 70   java.awt.image.DataBufferByte::setElem (21 bytes)   inline (hot)
                              @ 17   sun.java2d.StateTrackableDelegate::markDirty (6 bytes)   inline (hot)
                            @ 963   com.sun.imageio.plugins.png.RowFilter::filterRow (426 bytes)   too big
                            @ 971   com.sun.imageio.plugins.png.IDATOutputStream::write (17 bytes)   executed < MinInliningThreshold times
                            @ 990   com.sun.imageio.plugins.png.IDATOutputStream::write (43 bytes)   too big
                            @ 1041   javax.imageio.ImageWriter::processImageProgress (56 bytes)   too big
              s             @ 1045   javax.imageio.ImageWriter::abortRequested (5 bytes)   inline (hot)
                            @ 317   java.awt.Rectangle::<init> (26 bytes)   inline (hot)
                              @ 1   java.awt.geom.Rectangle2D::<init> (5 bytes)   inline (hot)
                                @ 1   java.awt.geom.RectangularShape::<init> (5 bytes)   inline (hot)
                                  @ 1   java.lang.Object::<init> (1 bytes)   inline (hot)
                            @ 325   java.awt.image.BufferedImage::getData (113 bytes)   too big
                            @ 372   sun.awt.image.ByteInterleavedRaster::getPixels (865 bytes)   too big
                            @ 377   java.awt.image.BufferedImage::getColorModel (5 bytes)   inline (hot)
                            @ 382   java.awt.image.ColorModel::isAlphaPremultiplied (5 bytes)   inline (hot)
                            @ 390   sun.awt.image.ByteInterleavedRaster::createCompatibleWritableRaster (13 bytes)   never executed
                            @ 399   java.awt.image.Raster::getMinX (5 bytes)   inline (hot)
                            @ 404   java.awt.image.Raster::getMinY (5 bytes)   inline (hot)
                            @ 409   java.awt.image.Raster::getWidth (5 bytes)   inline (hot)
                            @ 414   java.awt.image.Raster::getHeight (5 bytes)   inline (hot)
                            @ 423   java.awt.image.BufferedImage::getColorModel (5 bytes)   inline (hot)
                            @ 439   java.awt.image.Raster::getMinX (5 bytes)   inline (hot)
                            @ 444   java.awt.image.Raster::getMinY (5 bytes)   inline (hot)
                            @ 449   java.awt.image.Raster::getWidth (5 bytes)   inline (hot)
                            @ 454   java.awt.image.Raster::getHeight (5 bytes)   inline (hot)
                            @ 963   com.sun.imageio.plugins.png.RowFilter::filterRow (426 bytes)   too big
                            @ 971   com.sun.imageio.plugins.png.IDATOutputStream::write (17 bytes)   executed < MinInliningThreshold times
                            @ 990   com.sun.imageio.plugins.png.IDATOutputStream::write (43 bytes)   too big
                            @ 1041   javax.imageio.ImageWriter::processImageProgress (56 bytes)   too big
              s             @ 1045   javax.imageio.ImageWriter::abortRequested (5 bytes)   inline (hot)

6. Druhý demonstrační příklad – eliminace volání metod

Zkusme si nyní upravit příklad pro výpočet faktoriálu takovým způsobem, aby byl JIT překladač typu server donucen provést eliminaci volání metod, tj. method inlining. Je to ve skutečnosti velmi jednoduché, postačuje totiž vlastní výpočet prováděný původně v počítané programové smyčce v metodě factorial() „refaktorovat“ do zvláštní metody calcResult(), která pouze vrátí výsledek součinu svých dvou operandů. Nový demonstrační příklad vypadá následovně:

public class Factorial2 {
 
    public static int factorial(int n) {
        if (n <= 1) return 1;
        int result = 1;
        for (int i = 1; i <= n; i++) {
            result = calcResult(result, i);
        }
        return result;
    }
 
    public static int calcResult(int result, int i) {
        return result * i;
    }
 
    public static void testFactorial() {
        for (int i = 0; i < 16; i++) {
            System.out.println(i + "! = " + factorial(i));
        }
    }
 
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            testFactorial();
        }
    }
}

Snadno se můžeme přesvědčit o tom, že v bajtkódu je skutečně statická metoda calcResult() volána, protože vlastní překladač javac žádné významné optimalizace neprovádí:

  public static int factorial(int);
    Code:
       0: iload_0
       1: iconst_1
       2: if_icmpgt     7
       5: iconst_1
       6: ireturn
       7: iconst_1
       8: istore_1
       9: iconst_1
      10: istore_2
      11: iload_2
      12: iload_0
      13: if_icmpgt     28
      16: iload_1
      17: iload_2
      18: invokestatic  #2                  // Method calcResult:(II)I
      21: istore_1
      22: iinc          2, 1
      25: goto          11
      28: iload_1
      29: ireturn

Bajtkód metody calcResult() je skutečně velmi jednoduchý – typický adept na method inlining:

  public static int calcResult(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: imul
       3: ireturn

Nyní se podívejme na to, jak byla metoda factorial() přeložena JIT překladačem typu server:

Decoding compiled method 0x009bab88:
Code:
[Entry Point]
[Verified Entry Point]
[Constants]
  # {method} 'factorial' '(I)I' in 'Factorial2'
  # parm0:    ecx       = int
  #           [sp+0x10]  (sp of caller)
  0x009bac80: mov    %eax,0xffffc000(%esp)
  0x009bac87: push   %ebp
  0x009bac88: sub    $0x8,%esp          ;*synchronization entry
                                        ; - Factorial2::factorial@-1 (line 4)
  0x009bac8b: mov    $0x1,%eax
  0x009bac90: cmp    $0x1,%ecx
  0x009bac93: jle    0x009bad0a         ;*if_icmpgt
                                        ; - Factorial2::factorial@2 (line 4)
  0x009bac95: cmp    $0x1,%ecx
  0x009bac98: jl     0x009bad0a         ;*if_icmpgt
                                        ; - Factorial2::factorial@13 (line 6)
  0x009bac9a: cmp    $0x7ffffffe,%ecx
  0x009baca0: jg     0x009bad15         ;*iload_1
                                        ; - Factorial2::factorial@16 (line 7)
  0x009baca2: mov    %ecx,%edi
  0x009baca4: inc    %edi
  0x009baca5: add    $0xfffffffe,%ecx
  0x009baca8: mov    $0x80000000,%ebx
  0x009bacad: cmp    %ecx,%edi
  0x009bacaf: cmovl  %ebx,%ecx
  0x009bacb2: mov    $0x2,%edx
  0x009bacb7: cmp    $0x2,%ecx
  0x009bacba: jle    0x009bad25
  0x009bacbc: mov    $0x2,%eax
  0x009bacc1: jmp    0x009bace2
  ======================================
  0x009bacc3: mov    %esi,%ecx
  0x009bacc5: imul   %eax,%ecx
  0x009bacc8: mov    %ecx,%eax          ;*imul
                                        ; - Factorial2::calcResult@2 (line 13)
                                        ; - Factorial2::factorial@18 (line 7)
  ======================================
  0x009bacca: inc    %esi               ;*iinc
                                        ; - Factorial2::factorial@22 (line 6)
  0x009baccb: cmp    %edi,%esi
  0x009baccd: jl     0x009bacc3         ;*if_icmpgt
                                        ; - Factorial2::factorial@13 (line 6)
  0x009baccf: jmp    0x009bad0a
  0x009bacd1: nopw   0x0(%eax,%eax,1)
  0x009bacdc: xchg   %ax,%ax
  0x009bace0: mov    %esi,%edx          ;*imul
                                        ; - Factorial2::calcResult@2 (line 13)
                                        ; - Factorial2::factorial@18 (line 7)
  0x009bace2: mov    %edx,%ebx
  0x009bace4: add    $0x3,%ebx
  0x009bace7: mov    %edx,%esi
  0x009bace9: add    $0x4,%esi          ;*iinc
                                        ; - Factorial2::factorial@22 (line 6)
  0x009bacec: mov    %edx,%ebp
  0x009bacee: add    $0x2,%ebp
  ======================================
  0x009bacf1: inc    %edx
  0x009bacf2: imul   %eax,%edx
  0x009bacf5: imul   %ebp,%edx
  0x009bacf8: imul   %edx,%ebx
  0x009bacfb: mov    %esi,%eax
  0x009bacfd: imul   %ebx,%eax          ;*imul
                                        ; - Factorial2::calcResult@2 (line 13)
                                        ; - Factorial2::factorial@18 (line 7)
  ======================================
  0x009bad00: cmp    %ecx,%esi
  0x009bad02: jl     0x009bace0         ;*if_icmpgt
                                        ; - Factorial2::factorial@13 (line 6)
  0x009bad04: cmp    %edi,%esi
  0x009bad06: jl     0x009bacca
  0x009bad08: mov    %ebx,%eax
  0x009bad0a: add    $0x8,%esp
  0x009bad0d: pop    %ebp
  0x009bad0e: test   %eax,0x940000      ;   {poll_return}
  0x009bad14: ret
  0x009bad15: mov    %ecx,%ebp
  0x009bad17: mov    $0xffffff7e,%ecx
  0x009bad1c: xchg   %ax,%ax
  0x009bad1f: call   0x0099dd00         ; OopMap{off=164}
                                        ;*iload_1
                                        ; - Factorial2::factorial@16 (line 7)
                                        ;   {runtime_call}
  0x009bad24: int3                      ;*iload_1
                                        ; - Factorial2::factorial@16 (line 7)
  0x009bad25: mov    $0x2,%esi
  0x009bad2a: mov    $0x2,%eax
  0x009bad2f: mov    $0x1,%ebx
  0x009bad34: jmp    0x009bad04         ;*if_icmpgt
                                        ; - Factorial2::factorial@13 (line 6)
  0x009bad36: hlt
  0x009bad37: hlt
  0x009bad38: hlt
  0x009bad39: hlt
  0x009bad3a: hlt
  0x009bad3b: hlt
  0x009bad3c: hlt
  0x009bad3d: hlt
  0x009bad3e: hlt
  0x009bad3f: hlt
[Exception Handler]
[Stub Code]
  0x009bad40: jmp    0x009b7240         ;   {no_reloc}
[Deopt Handler Code]
  0x009bad45: push   $0x9bad45          ;   {section_word}
  0x009bad4a: jmp    0x0099e280         ;   {runtime_call}
  0x009bad4f: hlt

Z výpisu je patrné, že se skutečně provedl method inlining (žádné call) a taktéž rozbalení programové smyčky.

7. Použití intrinsic „funkcí“

Poslední optimalizační technika, kterou se v dnešním článku budeme zabývat, je založena na myšlence, že některé standardní metody jsou používané velmi často a současně i provádí časově náročné činnosti, takže by bylo vhodné, aby byly tyto metody již dopředu implementovány a optimalizovány. Jedná se například o některé metody (či přesněji řečeno funkce), které lze nalézt ve třídě java.lang.Math. Tyto metody/funkce (například goniometrické funkce) jsou implementovány takovým způsobem, že v některých případech vůbec nedojde k volání příslušných běhových nativních funkcí, ale namísto toho jsou přímo vygenerovány instrukce pro matematický koprocesor. Příkladem může být interní funkce JIT překladače generující dvojici bajtů, které ve strojovém kódu znamenají výpočet hodnoty sinus:

void Assembler::fsin() {
  emit_byte(0xD9);
  emit_byte(0xFE);
}

Dalším příkladem může být implementace metody String.indexOf(), kterou je taktéž možné nahradit optimalizovanou sekvencí instrukcí (hledání tak je možné provádět po osmici bajtů apod.).

8. Seznam všech „intrinsic“

Seznam všech tzv. intrinsic lze nalézt v hlavičkovém souboru vmSymbols.hpp (jedná se o součást OpenJDK):

Jméno generující funkce        Třída
_hashCode,                     java_lang_Object
_getClass,                     java_lang_Object
_clone,                        java_lang_Object
_dabs,                         java_lang_Math
_dsin,                         java_lang_Math
_dcos,                         java_lang_Math
_dtan,                         java_lang_Math
_datan2,                       java_lang_Math
_dsqrt,                        java_lang_Math
_dlog,                         java_lang_Math
_dlog10,                       java_lang_Math
_dpow,                         java_lang_Math
_dexp,                         java_lang_Math
_min,                          java_lang_Math
_max,                          java_lang_Math
_floatToRawIntBits,            java_lang_Float
_floatToIntBits,               java_lang_Float
_intBitsToFloat,               java_lang_Float
_doubleToRawLongBits,          java_lang_Double
_doubleToLongBits,             java_lang_Double
_longBitsToDouble,             java_lang_Double
_numberOfLeadingZeros_i,       java_lang_Integer
_numberOfLeadingZeros_l,       java_lang_Long
_numberOfTrailingZeros_i,      java_lang_Integer
_numberOfTrailingZeros_l,      java_lang_Long
_bitCount_i,                   java_lang_Integer
_bitCount_l,                   java_lang_Long
_reverseBytes_i,               java_lang_Integer
_reverseBytes_l,               java_lang_Long
_reverseBytes_c,               java_lang_Character
_reverseBytes_s,               java_lang_Short
_identityHashCode,             java_lang_System
_currentTimeMillis,            java_lang_System
_nanoTime,                     java_lang_System
_arraycopy,                    java_lang_System
_isInterrupted,                java_lang_Thread
_currentThread,                java_lang_Thread
_isAssignableFrom,             java_lang_Class
_isInstance,                   java_lang_Class
_getModifiers,                 java_lang_Class
_isInterface,                  java_lang_Class
_isArray,                      java_lang_Class
_isPrimitive,                  java_lang_Class
_getSuperclass,                java_lang_Class
_getComponentType,             java_lang_Class
_getClassAccessFlags,          sun_reflect_Reflection
_getLength,                    java_lang_reflect_Array
_getCallerClass,               sun_reflect_Reflection
_newArray,                     java_lang_reflect_Array
_copyOf,                       java_util_Arrays
_copyOfRange,                  java_util_Arrays
_equalsC,                      java_util_Arrays
_compareTo,                    java_lang_String
_indexOf,                      java_lang_String
_equals,                       java_lang_String
_checkIndex,                   java_nio_Buffer
_Reference_get,                java_lang_ref_Reference
_get_AtomicLong,               sun_misc_AtomicLongCSImpl
_attemptUpdate,                sun_misc_AtomicLongCSImpl
_allocateInstance,             sun_misc_Unsafe
_copyMemory,                   sun_misc_Unsafe
_park,                         sun_misc_Unsafe
_unpark,                       sun_misc_Unsafe
_getObject,                    sun_misc_Unsafe
_getBoolean,                   sun_misc_Unsafe
_getByte,                      sun_misc_Unsafe
_getShort,                     sun_misc_Unsafe
_getChar,                      sun_misc_Unsafe
_getInt,                       sun_misc_Unsafe
_getLong,                      sun_misc_Unsafe
_getFloat,                     sun_misc_Unsafe
_getDouble,                    sun_misc_Unsafe
_putObject,                    sun_misc_Unsafe
_putBoolean,                   sun_misc_Unsafe
_putByte,                      sun_misc_Unsafe
_putShort,                     sun_misc_Unsafe
_putChar,                      sun_misc_Unsafe
_putInt,                       sun_misc_Unsafe
_putLong,                      sun_misc_Unsafe
_putFloat,                     sun_misc_Unsafe
_putDouble,                    sun_misc_Unsafe
_getObjectVolatile,            sun_misc_Unsafe
_getBooleanVolatile,           sun_misc_Unsafe
_getByteVolatile,              sun_misc_Unsafe
_getShortVolatile,             sun_misc_Unsafe
_getCharVolatile,              sun_misc_Unsafe
_getIntVolatile,               sun_misc_Unsafe
_getLongVolatile,              sun_misc_Unsafe
_getFloatVolatile,             sun_misc_Unsafe
_getDoubleVolatile,            sun_misc_Unsafe
_putObjectVolatile,            sun_misc_Unsafe
_putBooleanVolatile,           sun_misc_Unsafe
_putByteVolatile,              sun_misc_Unsafe
_putShortVolatile,             sun_misc_Unsafe
_putCharVolatile,              sun_misc_Unsafe
_putIntVolatile,               sun_misc_Unsafe
_putLongVolatile,              sun_misc_Unsafe
_putFloatVolatile,             sun_misc_Unsafe
_putDoubleVolatile,            sun_misc_Unsafe
_getByte_raw,                  sun_misc_Unsafe
_getShort_raw,                 sun_misc_Unsafe
_getChar_raw,                  sun_misc_Unsafe
_getInt_raw,                   sun_misc_Unsafe
_getLong_raw,                  sun_misc_Unsafe
_getFloat_raw,                 sun_misc_Unsafe
_getDouble_raw,                sun_misc_Unsafe
_getAddress_raw,               sun_misc_Unsafe
_putByte_raw,                  sun_misc_Unsafe
_putShort_raw,                 sun_misc_Unsafe
_putChar_raw,                  sun_misc_Unsafe
_putInt_raw,                   sun_misc_Unsafe
_putLong_raw,                  sun_misc_Unsafe
_putFloat_raw,                 sun_misc_Unsafe
_putDouble_raw,                sun_misc_Unsafe
_putAddress_raw,               sun_misc_Unsafe
_compareAndSwapObject,         sun_misc_Unsafe
_compareAndSwapInt,            sun_misc_Unsafe
_putOrderedObject,             sun_misc_Unsafe
_putOrderedLong,               sun_misc_Unsafe
_putOrderedInt,                sun_misc_Unsafe
_prefetchRead,                 sun_misc_Unsafe
_prefetchWrite,                sun_misc_Unsafe
_prefetchReadStatic,           sun_misc_Unsafe
_prefetchWriteStatic,          sun_misc_Unsafe
_fillInStackTrace,             java_lang_Throwable
_StringBuilder_void,           java_lang_StringBuilder
_StringBuilder_int,            java_lang_StringBuilder
_StringBuilder_String,         java_lang_StringBuilder
_StringBuilder_append_char,    java_lang_StringBuilder
_StringBuilder_append_int,     java_lang_StringBuilder
_StringBuilder_append_String , java_lang_StringBuilder
_StringBuilder_toString,       java_lang_StringBuilder
_StringBuffer_void,            java_lang_StringBuffer
_StringBuffer_int,             java_lang_StringBuffer
_StringBuffer_String,          java_lang_StringBuffer
_StringBuffer_append_char,     java_lang_StringBuffer
_StringBuffer_append_int,      java_lang_StringBuffer
_StringBuffer_append_String,   java_lang_StringBuffer
_StringBuffer_toString,        java_lang_StringBuffer
_Integer_toString,             java_lang_Integer
_String_String,                java_lang_String
_Object_init,                  java_lang_Object
_invoke,                       java_lang_reflect_Method
_checkSpreadArgument,          java_lang_invoke_MethodHandleNatives
_invokeExact,                  java_lang_invoke_MethodHandle
_invokeGeneric,                java_lang_invoke_MethodHandle
_invokeVarargs,                java_lang_invoke_MethodHandle
_invokeDynamic,                java_lang_invoke_InvokeDynamic
_booleanValue,                 java_lang_Boolean
_byteValue,                    java_lang_Byte
_charValue,                    java_lang_Character
_shortValue,                   java_lang_Short
_intValue,                     java_lang_Integer
_longValue,                    java_lang_Long
_floatValue,                   java_lang_Float
_doubleValue,                  java_lang_Double
_Boolean_valueOf,              java_lang_Boolean
_Byte_valueOf,                 java_lang_Byte
_Character_valueOf,            java_lang_Character
_Short_valueOf,                java_lang_Short
_Integer_valueOf,              java_lang_Integer
_Long_valueOf,                 java_lang_Long
_Float_valueOf,                java_lang_Float
_Double_valueOf,               java_lang_Double

9. Metoda System.arraycopy()

Již v úvodní kapitole jsme si řekli, že jednou z těchto tzv. intrinsic je i metoda System.arraycopy(), jejíž kód je již dopředu připraven, a to hned v několika variantách optimalizovaných pro různé případy, které mohou v praxi nastat (překryv polí, zarovnání či naopak nezarovnání polí atd.). Metoda System.arraycopy() má následující hlavičku:

public static void arraycopy(Object src,
                             int srcPos,
                             Object dest,
                             int destPos,
                             int length)

Tato metoda slouží ke kopii prvků z jednoho pole do pole jiného, přičemž je možné zvolit jak offsety prvků (nemusí se začínat prvkem s indexem 0), tak i celkový počet kopírovaných prvků. Podívejme se nyní na jednoduchý demonstrační příklad, kde se metoda System.arraycopy() volá s různými parametry:

cyber23

public class ArrayCopyTest {
    static int[] src = new int[30000];
    static int[] dest = new int[30000];
 
    public static void testArrayCopy1(int offset, int length) {
        System.arraycopy(src, 0, dest, 0, length);
    }
 
    public static void testArrayCopy2(int offset, int length) {
        System.arraycopy(src, offset, dest, 0, length);
    }
 
    public static void testArrayCopy3(int offset, int length) {
        System.arraycopy(src, 0, dest, offset, length);
    }
 
    public static void testArrayCopy4(int offset, int length) {
        System.arraycopy(src, 0, src, 0, length);
    }
 
    public static void testArrayCopy5(int offset, int length) {
        System.arraycopy(src, offset, src, 0, length);
    }
 
    public static void testArrayCopy6(int offset, int length) {
        System.arraycopy(src, 0, src, offset, length);
    }
 
    public static void main(String[] args) {
        for (int i = 0; i < 10000; i++) {
            testArrayCopy1(i, i);
            testArrayCopy2(i, i);
            testArrayCopy3(i, i);
            testArrayCopy4(i, i);
            testArrayCopy5(i, i);
            testArrayCopy6(i, i);
        }
    }
}

Při překladu dojde k nahrazení volání System.arraycopy() několika různými nativními a optimalizovanými variantami smyčky pro kopii prvků rozdílných typů s různým zarovnáním, překryvem polí atd. Konkrétní výsledky si ukážeme v následující části tohoto seriálu.

10. Repositář se zdrojovými kódy všech demonstračních i testovacích příkladů

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ů:

11. 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

Byl pro vás článek přínosný?