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
6. Druhý demonstrační příklad – eliminace volání metod
10. Repositář se zdrojovými kódy všech demonstračních i testovacích příkladů
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í:
- 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).
- 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.
- 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.
- 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).
- 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í mul a imul 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 call a return, 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.org/people/ptisnovs/jvm-tools/file/05d05b5fd08d/jit/MandelbrotRenderer.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:
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ů:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | Factorial.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/d6e81b348112/jit/Factorial.java |
2 | Factorial2.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/d6e81b348112/jit/Factorial2.java |
3 | ArrayCopyTest.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/d6e81b348112/jit/ArrayCopyTest.java |
11. Odkazy na Internetu
- GC safe-point (or safepoint) and safe-region
http://xiao-feng.blogspot.cz/2008/01/gc-safe-point-and-safe-region.html - Safepoints in HotSpot JVM
http://blog.ragozin.info/2012/10/safepoints-in-hotspot-jvm.html - Java theory and practice: Synchronization optimizations in Mustang
http://www.ibm.com/developerworks/java/library/j-jtp10185/ - How to build hsdis
http://hg.openjdk.java.net/jdk7/hotspot/hotspot/file/tip/src/share/tools/hsdis/README - Java SE 6 Performance White Paper
http://www.oracle.com/technetwork/java/6-performance-137236.html - Lukas Stadler's Blog
http://classparser.blogspot.cz/2010/03/hsdis-i386dll.html - How to build hsdis-amd64.dll and hsdis-i386.dll on Windows
http://dropzone.nfshost.com/hsdis.htm - PrintAssembly
https://wikis.oracle.com/display/HotSpotInternals/PrintAssembly - The Java Virtual Machine Specification: 3.14. Synchronization
http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-3.html#jvms-3.14 - The Java Virtual Machine Specification: 8.3.1.4. volatile Fields
http://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1.4 - The Java Virtual Machine Specification: 17.4. Memory Model
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4 - The Java Virtual Machine Specification: 17.7. Non-atomic Treatment of double and long
http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.7 - Open Source ByteCode Libraries in Java
http://java-source.net/open-source/bytecode-libraries - ASM Home page
http://asm.ow2.org/ - Seznam nástrojů využívajících projekt ASM
http://asm.ow2.org/users.html - ObjectWeb ASM (Wikipedia)
http://en.wikipedia.org/wiki/ObjectWeb_ASM - Java Bytecode BCEL vs ASM
http://james.onegoodcookie.com/2005/10/26/java-bytecode-bcel-vs-asm/ - BCEL Home page
http://commons.apache.org/bcel/ - Byte Code Engineering Library (před verzí 5.0)
http://bcel.sourceforge.net/ - Byte Code Engineering Library (verze >= 5.0)
http://commons.apache.org/proper/commons-bcel/ - BCEL Manual
http://commons.apache.org/bcel/manual.html - Byte Code Engineering Library (Wikipedia)
http://en.wikipedia.org/wiki/BCEL - BCEL Tutorial
http://www.smfsupport.com/support/java/bcel-tutorial!/ - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html - Bytecode Outline plugin for Eclipse (screenshoty + info)
http://asm.ow2.org/eclipse/index.html - Javassist
http://www.jboss.org/javassist/ - Byteman
http://www.jboss.org/byteman - Java programming dynamics, Part 7: Bytecode engineering with BCEL
http://www.ibm.com/developerworks/java/library/j-dyn0414/ - The JavaTM Virtual Machine Specification, Second Edition
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - aspectj (Eclipse)
http://www.eclipse.org/aspectj/ - Aspect-oriented programming (Wikipedia)
http://en.wikipedia.org/wiki/Aspect_oriented_programming - AspectJ (Wikipedia)
http://en.wikipedia.org/wiki/AspectJ - EMMA: a free Java code coverage tool
http://emma.sourceforge.net/ - Cobertura
http://cobertura.sourceforge.net/ - jclasslib bytecode viewer
http://www.ej-technologies.com/products/jclasslib/overview.html