Obsah
1. Pohled pod kapotu JVM – základy optimalizace aplikací naprogramovaných v Javě (5)
2. Vkládání safe-pointů do generovaného kódu
3. Způsob implementace safe-pointů na platformě x86
5. Synchronizované metody a synchronizované bloky
6. Demonstrační benchmark SynchronizationTest1
8. Výsledky spuštění benchmarku
9. 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 nejdříve řekneme, jakým způsobem vyřešili autoři HotSpotu problematiku dvou protichůdných hledisek, ke kterým je zapotřebí přihlížet při vytváření takzvaných safe-pointů. Posléze se seznámíme s principem generování safe-pointů na platformě x86 a x86_64; protože se jedná (alespoň podle mého názoru) o dosti elegantní řešení celé problematiky, které je „optimistické“ v tom smyslu, že se může generovat poměrně velké množství safe-pointů, ovšem na druhou stranu se optimisticky předpokládá, že se těmito safe-pointy bude ve většině případů pouze procházet bez nutnosti pozastavení vlákna.
Ve druhé části článku se budeme zabývat použitím synchronizovaných metod a synchronizovaných bloků. Zajímavé bude porovnání výkonnosti metod přistupujících k atributům objektu s využitím synchronizace, popř. alternativně metod přistupujících k volatilním atributům. Právě zde se ukáže jeden z důvodů, proč v Javě vůbec atributy s modifikátorem volatile existují, i když by se mohlo zdát, že jejich použití nám oproti explicitní synchronizaci nepřináší žádné viditelné výhody (i když sémantika synchronizovaného přístupu a využití volatilních atributů je odlišná!).
2. Vkládání safe-pointů do generovaného kódu
V předchozí části tohoto seriálu jsme si řekli základní informace o takzvaných safe-pointech. Připomeňme si, že se jedná o taková místa v generovaném strojovém kódu, v nichž je umožněno bezpečné zastavení vlákna či vláken. Safe pointy jsou představovány určitou instrukcí či sekvencí instrukcí, kterou si podrobněji popíšeme ve třetí kapitole. Taktéž jsme si řekli, že je důležité správné určení těch oblastí kódu, v nichž mají být safe-pointy použity, přičemž je nutné brát ohled na dvě navzájem protichůdná hlediska. Na jednu stranu by mělo být safe-pointů vytvořeno co nejvíc, aby bylo možné vlákna pozastavit v prakticky každý okamžik, což je vyžadováno například správci paměti typu stop-the-world (STW) (taktéž se přesněji detekují deadlocky). Na stranu druhou je však vkládání safe-pointů náročné jak na operační paměť (pro nové instrukce i pro mapu referencí na objekty), tak i na rychlost běhu přeloženého (JITovaného) kódu.
Aby se obě dvě protichůdná hlediska zmíněná v předchozím odstavci nějakým způsobem mohla vhodně zkombinovat a uplatnit v praxi, jsou safe-pointy vytvářeny na určitých významných místech v generovaném zdrojovém kódu. Například se jedná o instrukce umístěné před nepodmíněné i podmíněné skoky, taktéž před strojový kód představující vykonávání instrukcí tableswitch a lookupswitch, před návratem z metod (všechny instrukce typu return) apod. Když se nad tímto chováním zamyslíme, zjistíme, že je automaticky zajištěno i vkládání safe-pointů do programových smyček, které jsou na úrovni bajtkódu a posléze i na úrovni generovaného strojového kódu představovány kombinací podmíněných a nepodmíněných skoků, podobně jako Javovské jazykové konstrukce určené pro větvení běhu programu. Jakým způsobem jsou safe-pointy konkrétně generovány na platformě x86 si stručně popíšeme v navazující kapitole.
3. Způsob implementace safe-pointů na platformě x86
Způsob implementace safe-pointů je v moderních virtuálních strojích Javy založených na HotSpotu řešen zajímavou technikou zajišťující rychlý přesun přes safe-point ve chvíli, kdy není nutné vlákno zastavit. Každý safe-point je zde představován jedinou instrukcí, která načítá slovo z operační paměti z určité adresy, přičemž načtené slovo se ihned zahodí (přesněji řečeno se ignoruje). Na platformě x86 je načtení řešeno instrukcí test eax, [adresa] resp. test rax, [adresa], což má tu výhodu, že se čtením nepřepíše obsah žádného registru, pouze se změní obsah příznakového bitu Z (zero), který je však v dalších instrukcích stejně ignorován. Poznámka – instrukce test A,B provádí stejnou operaci jako instrukce and A,B, výsledek výpočtu logického součinu bit po bitu se však nikam nezapisuje, což přibližně odpovídá rozdílům mezi instrukcemi sub A,B a cmp A,B.
Adresa slova čteného instrukcí test je umístěna ve speciální stránce paměti nazývané poll page. Při normálním běhu vláken je čtení slova instrukcí test provedeno velmi rychle, protože je obsah poll page uložen ve vyrovnávacích pamětech všech procesorových jader. Ovšem ve chvíli, kdy je zapotřebí pozastavit všechna vlákna, odmapuje virtuální stroj Javy pomocí systémového volání poll page. To při pokusu o čtení slova instrukcí test povede ke vzniku výpadku stránky (page fault), která je ošetřena handlerem, jenž je součástí JVM. Vzhledem k tomu, že k safe-pointu dřív či později dojdou všechna aplikační vlákna, budou všechna pozastavena právě při výpadku stránky. Důležité je, že odmapování poll page je provedeno na globální úrovni – platí tedy pro všechna procesorová jádra.
4. Ukázkový příklad
Podívejme se nyní ještě jednou na výpis přeloženého strojového kódu metody ArrayTest3.test(). Nyní nás však nebude zajímat, jak přesně se přeložila programová smyčka v této metodě, ale kde jsou v ní umístěny safe-pointy. Ty jsou ve výpisu zvýrazněny:
[Entry Point] [Verified Entry Point] [Constants] # {method} 'test' '()V' in 'ArrayTest3' # [sp+0x20] (sp of caller) 0x00a00a00: mov %eax,0xffffc000(%esp) 0x00a00a07: push %ebp 0x00a00a08: sub $0x18,%esp ;*sipush ; - ArrayTest3::test@0 (line 47) 0x00a00a0b: mov $0x1388,%ebx 0x00a00a10: mov $0x2fb1c6a8,%edx ; {oop({type array int})} 0x00a00a15: mov %ebx,%edi 0x00a00a17: cmp $0xffffff,%ebx 0x00a00a1d: ja 0x00a00ab7 0x00a00a23: mov $0x13,%esi 0x00a00a28: lea (%esi,%ebx,4),%esi 0x00a00a2b: and $0xfffffff8,%esi 0x00a00a2e: mov %fs:0x0,%ecx 0x00a00a36: mov 0xfffffff4(%ecx),%ecx 0x00a00a39: mov 0x34(%ecx),%eax 0x00a00a3c: lea (%eax,%esi,1),%esi 0x00a00a3f: cmp 0x3c(%ecx),%esi 0x00a00a42: ja 0x00a00ab7 0x00a00a48: mov %esi,0x34(%ecx) 0x00a00a4b: sub %eax,%esi 0x00a00a4d: movl $0x1,(%eax) 0x00a00a53: mov %edx,0x4(%eax) 0x00a00a56: mov %ebx,0x8(%eax) 0x00a00a59: sub $0xc,%esi 0x00a00a5c: je 0x00a00a82 0x00a00a62: xor %ebx,%ebx 0x00a00a64: shr $0x3,%esi 0x00a00a67: jae 0x00a00a77 0x00a00a6d: mov %ebx,0xc(%eax,%esi,8) 0x00a00a71: je 0x00a00a82 0x00a00a77: mov %ebx,0x8(%eax,%esi,8) 0x00a00a7b: mov %ebx,0x4(%eax,%esi,8) 0x00a00a7f: dec %esi 0x00a00a80: jne 0x00a00a77 ;*newarray ; - ArrayTest3::test@3 (line 47) 0x00a00a82: mov $0x0,%esi 0x00a00a87: jmp 0x00a00aa4 ;*iload_2 ; - ArrayTest3::test@11 (line 49) 0x00a00a8c: mov $0x1388,%edi 0x00a00a91: cmp %esi,%edi 0x00a00a93: jbe 0x00a00abe 0x00a00a99: mov %esi,0xc(%eax,%esi,4) ;*iastore ; - ArrayTest3::test@19 (line 50) 0x00a00a9d: inc %esi ; OopMap{eax=Oop off=158} ;*goto ; - ArrayTest3::test@23 (line 49) ; ------------------------------------------------------------------- 0x00a00a9e: test %eax,0x940100 ;*goto ; - ArrayTest3::test@23 (line 49) ; {poll} ; ------------------------------------------------------------------- 0x00a00aa4: cmp $0x1388,%esi 0x00a00aaa: jl 0x00a00a8c ;*if_icmpge ; - ArrayTest3::test@13 (line 49) 0x00a00aac: add $0x18,%esp 0x00a00aaf: pop %ebp ; ------------------------------------------------------------------- 0x00a00ab0: test %eax,0x940100 ; {poll_return} ; ------------------------------------------------------------------- 0x00a00ab6: ret 0x00a00ab7: call 0x009fef00 ; OopMap{off=188} ;*newarray ; - ArrayTest3::test@3 (line 47) ; {runtime_call} 0x00a00abc: jmp 0x00a00a82 0x00a00abe: mov %esi,(%esp) 0x00a00ac1: call 0x009fe250 ; OopMap{eax=Oop off=198} ;*iastore ; - ArrayTest3::test@19 (line 50) ; {runtime_call} 0x00a00ac6: nop 0x00a00ac7: nop 0x00a00ac8: mov %fs:0x0,%esi 0x00a00ad0: mov 0xfffffff4(%esi),%esi 0x00a00ad3: mov 0x188(%esi),%eax 0x00a00ad9: movl $0x0,0x188(%esi) 0x00a00ae3: movl $0x0,0x18c(%esi) 0x00a00aed: add $0x18,%esp 0x00a00af0: pop %ebp 0x00a00af1: jmp 0x009abb80 ; {runtime_call} 0x00a00af6: hlt 0x00a00af7: hlt 0x00a00af8: hlt 0x00a00af9: hlt 0x00a00afa: hlt 0x00a00afb: hlt 0x00a00afc: hlt 0x00a00afd: hlt 0x00a00afe: hlt 0x00a00aff: hlt [Exception Handler] [Stub Code] 0x00a00b00: call 0x009ff880 ; {no_reloc} 0x00a00b05: push $0x6eb63690 ; {external_word} 0x00a00b0a: call 0x00a00b0f 0x00a00b0f: pusha 0x00a00b10: call 0x6ea45f60 ; {runtime_call} 0x00a00b15: hlt [Deopt Handler Code] 0x00a00b16: push $0xa00b16 ; {section_word} 0x00a00b1b: jmp 0x0099ca70 ; {runtime_call}
Poznámka: načítaná adresa, popř. celá adresa poll page může být při každém novém spuštění JVM odlišná.
5. Synchronizované metody a synchronizované bloky
Další problematika, kterou se budeme v dnešním článku zabývat, souvisí s explicitní synchronizací vláken v Javě. Programovací jazyk Java již od svého vzniku podporoval provádění synchronizace vláken s využitím takzvaných synchronizovaných metod a synchronizovaných bloků. Připomeňme si krátce, že synchronizované metody lze snadno poznat podle modifikátoru synchronized uvedeného v deklaraci metody. Synchronizované bloky začínají taktéž klíčovým slovem synchronized, za nímž se uvádí identifikátor objektu použitého pro synchronizaci. V Javě lze pro synchronizaci použít prakticky každý objekt, protože každý objekt vytvořený na haldě obsahuje (ve své hlavičce neviditelné javovskému programátorovi) zámek, jenž může nějaké vlákno získat, vrátit ho či čekat na jeho uvolnění. U synchronizovaných metod, u nichž se žádný objekt nezadává, se pro synchronizaci použije this (nestatické metody) či přímo objekt představující obraz třídy (statické metody).
V diskusi pod předchozím článkem zazněl zajímavý (a na školeních poměrně často pokládaný) dotaz – když přihlédneme k tomu, jaké problémy způsobuje použití volatilních atributů, není lepší se zcela od modifikátoru volatile odvrátit a používat pouze explicitní synchronizaci při přístupu k atributům? Pokud odhlédneme od toho, že sémantika volatilních atributů a synchronizace přístupu k nevolatilním atributům je odlišná (což někdy může a někdy nemusí vadit), ukazuje se, že problém může nastat především v oblasti výpočetního výkonu. V navazujících kapitolách se pokusíme tento problém ilustrovat na velmi jednoduchém demonstračním příkladu.
6. Demonstrační benchmark SynchronizationTest1
Test rychlosti provádění základních operací s atributy typu int budeme provádět s využitím velmi jednoduchého demonstračního příkladu nazvaného SynchronizationTest1. V této třídě jsou deklarovány čtyři celočíselné atributy x, y, z a v, přičemž poslední atribut je volatilní. Dále jsou v této třídě deklarovány čtyři metody testX(), testY(), testZ() a testV(), které pouze zvýší obsah příslušného atributu (metoda testX() zvýší hodnotu atributu x atd.). Důležité je, že metoda testX() nepoužívá žádnou formu synchronizace, metoda testY() je synchronizovaná, metoda testZ() obsahuje synchronizovaný blok a konečně metoda testV() přistupuje k volatilnímu atributu:
/** * Jednoduchy benchmark pro porovnani rychlostnich * rozdilu mezi pouzitim synchronizovanych metod * a synchronizovanych bloku. */ public class SynchronizationTest1 { // dostatecny pocet iteraci pro spusteni JITu private final static int ITERATIONS = 5000000; // zamezime tomu, aby se do casu behu benchmarku // zapocital i cas JITu private final static int WARMUP = 3; public int x = 0; public int y = 0; public int z = 0; public volatile int v = 0; // test s nesynchronizovanou metodou private void testX() { x++; } // test se synchronizovanou metodou private synchronized void testY() { y++; } // test s metodou se synchronizovanym blokem private void testZ() { synchronized(this) { z++; } } // test s metodou vyuzivajici volatilni atribut private void testV() { v++; } // spusteni benchmarku public static void main(String[] args) { SynchronizationTest1 test = new SynchronizationTest1(); long t1, t2, delta_t; long sumTestX=0, sumTestY=0, sumTestZ=0, sumTestV=0; // provest zadany pocet testu for (int i = 0; i < 10; i++) { // provest test a zmerit cas behu testu t1 = System.nanoTime(); for (int j = 0; j < ITERATIONS; j++) { test.testX(); } t2 = System.nanoTime(); delta_t = t2 - t1; if (i > WARMUP) { sumTestX += delta_t; } // vypis casu pro prvni test System.out.format("Round #%2d testX() time: %,12d ns\n", i, delta_t); // provest test a zmerit cas behu testu t1 = System.nanoTime(); for (int j = 0; j < ITERATIONS; j++) { test.testY(); } t2 = System.nanoTime(); delta_t = t2 - t1; if (i > WARMUP) { sumTestY += delta_t; } // vypis casu pro druhy test System.out.format("Round #%2d testY() time: %,12d ns\n", i, delta_t); // provest test a zmerit cas behu testu t1 = System.nanoTime(); for (int j = 0; j < ITERATIONS; j++) { test.testZ(); } t2 = System.nanoTime(); delta_t = t2 - t1; if (i > WARMUP) { sumTestZ += delta_t; } // vypis casu pro treti test System.out.format("Round #%2d testZ() time: %,12d ns\n", i, delta_t); // provest test a zmerit cas behu testu t1 = System.nanoTime(); for (int j = 0; j < ITERATIONS; j++) { test.testV(); } t2 = System.nanoTime(); delta_t = t2 - t1; if (i > WARMUP) { sumTestV += delta_t; } // vypis casu pro treti test System.out.format("Round #%2d testV() time: %,12d ns\n", i, delta_t); } // vypis vsech ctyr kumulativnich casu System.out.format("Cumulative time for testX(): %,12d ns\n", sumTestX); System.out.format("Cumulative time for testY(): %,12d ns\n", sumTestY); System.out.format("Cumulative time for testZ(): %,12d ns\n", sumTestZ); System.out.format("Cumulative time for testV(): %,12d ns\n", sumTestV); } }
Otázka #1 pro vážené čtenáře: povšimněte si, že se v metodách testY() a testZ() čte a zapisuje do nevolatilního atributu. Co se stane v případě, že tyto metody budou volány z většího množství vláken – budou jednotlivá vlákna skutečně načítat a zapisovat korektní hodnotu?
Otázka #2: pokud budeme mít dvě souběžně běžící vlákna, z nichž každé zavolá 10000× metody testX(), testY(), testZ() a testV(), lze na konci běhu obou vláken přesně říci, jaké budou hodnoty jednotlivých atributů x, y, z a v? Bude skutečně v každém atributu uložena hodnota 20000?
Otázka #3: jak se liší sémantika zápisu synchronized(this) {z++;} od zápisu v++; v případě, že z je běžný nevolatilní celočíselný atribut, zatímco v je volatilní celočíselný atribut? A jaký by byl rozdíl v případě synchronized(this) {z++;z++;} versus v++;v++;? (předpokládejme, že oba atributy sleduje další vlákno)
7. Bajtkód testovacích metod
Pro zajímavost se podívejme, jak se liší (popř. jak se neliší) bajtkódy všech čtyř testovacích metod testX(), testY(), testZ() a testV():
private void testX(); Code: 0: aload_0 1: dup 2: getfield #2; //Field x:I 5: iconst_1 6: iadd 7: putfield #2; //Field x:I 10: return private synchronized void testY(); Code: 0: aload_0 1: dup 2: getfield #3; //Field y:I 5: iconst_1 6: iadd 7: putfield #3; //Field y:I 10: return private void testZ(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter 4: aload_0 5: dup 6: getfield #4; //Field z:I 9: iconst_1 10: iadd 11: putfield #4; //Field z:I 14: aload_1 15: monitorexit 16: goto 24 19: astore_2 20: aload_1 21: monitorexit 22: aload_2 23: athrow 24: return Exception table: from to target type 4 16 19 any 19 22 19 any private void testV(); Code: 0: aload_0 1: dup 2: getfield #5; //Field v:I 5: iconst_1 6: iadd 7: putfield #5; //Field v:I 10: return
Zajímavé je, že se metody testX(), testY() a testV() liší pouze minimálně – u jedné metody je nastaven modifikátor synchronized a poslední zmíněná metoda přistupuje k volatilnímu atributu:
Naproti tomu je bajtkód metody testZ() velmi odlišný a to kvůli tomu, že bylo nutné implementovat celou logiku zamykání pomocí instrukcí monitorenter a monitorexit, logiku vyhození výjimky atd. Jak uvidíme dále, je tento složitější bajtkód důvodem pro pomalejší běh této metody v režimu interpretru.
8. Výsledky spuštění benchmarku
Podívejme se nyní, jak vypadají výsledky po spuštění našeho jednoduchého testovacího benchmarku SynchronizationTest1.
Spuštění java -Xint SynchronizationTest1 (interpret):
Round # 0 testX() time: 970,466,598 ns Round # 0 testY() time: 1,190,523,646 ns Round # 0 testZ() time: 1,846,322,309 ns Round # 0 testV() time: 969,063,736 ns Round # 1 testX() time: 968,999,480 ns Round # 1 testY() time: 1,192,601,985 ns Round # 1 testZ() time: 1,844,591,292 ns Round # 1 testV() time: 972,872,389 ns Round # 2 testX() time: 968,367,626 ns Round # 2 testY() time: 1,188,980,578 ns Round # 2 testZ() time: 1,851,114,044 ns Round # 2 testV() time: 971,430,517 ns Round # 3 testX() time: 963,871,942 ns Round # 3 testY() time: 1,187,272,614 ns Round # 3 testZ() time: 1,865,358,870 ns Round # 3 testV() time: 972,481,696 ns Round # 4 testX() time: 966,426,457 ns Round # 4 testY() time: 1,190,325,657 ns Round # 4 testZ() time: 1,847,068,764 ns Round # 4 testV() time: 975,294,205 ns Round # 5 testX() time: 967,144,284 ns Round # 5 testY() time: 1,189,177,957 ns Round # 5 testZ() time: 1,849,168,609 ns Round # 5 testV() time: 969,951,276 ns Round # 6 testX() time: 973,176,547 ns Round # 6 testY() time: 1,188,013,079 ns Round # 6 testZ() time: 1,848,092,352 ns Round # 6 testV() time: 975,289,734 ns Round # 7 testX() time: 967,232,774 ns Round # 7 testY() time: 1,190,537,843 ns Round # 7 testZ() time: 1,847,953,645 ns Round # 7 testV() time: 971,987,707 ns Round # 8 testX() time: 966,804,510 ns Round # 8 testY() time: 1,192,591,317 ns Round # 8 testZ() time: 1,852,017,638 ns Round # 8 testV() time: 973,211,397 ns Round # 9 testX() time: 966,083,888 ns Round # 9 testY() time: 1,189,510,967 ns Round # 9 testZ() time: 1,863,161,440 ns Round # 9 testV() time: 971,858,433 ns Cumulative time for testX(): 5,806,868,460 ns Cumulative time for testY(): 7,140,156,820 ns Cumulative time for testZ(): 11,107,462,448 ns Cumulative time for testV(): 5,837,592,752 ns
![](https://i.iinfo.cz/images/122/javagraph1.png)
Graf porovnávající běh benchmarku v režimu interpretru.
Spuštění java -client SynchronizationTest1 (JIT typu client):
Round # 0 testX() time: 25,535,787 ns Round # 0 testY() time: 170,192,150 ns Round # 0 testZ() time: 170,291,063 ns Round # 0 testV() time: 18,921,960 ns Round # 1 testX() time: 12,644,486 ns Round # 1 testY() time: 170,186,085 ns Round # 1 testZ() time: 170,131,391 ns Round # 1 testV() time: 18,969,451 ns Round # 2 testX() time: 12,676,542 ns Round # 2 testY() time: 170,352,807 ns Round # 2 testZ() time: 170,173,507 ns Round # 2 testV() time: 18,927,892 ns Round # 3 testX() time: 12,628,769 ns Round # 3 testY() time: 170,211,789 ns Round # 3 testZ() time: 170,345,333 ns Round # 3 testV() time: 19,061,081 ns Round # 4 testX() time: 12,685,341 ns Round # 4 testY() time: 170,127,126 ns Round # 4 testZ() time: 170,114,853 ns Round # 4 testV() time: 19,011,776 ns Round # 5 testX() time: 12,646,790 ns Round # 5 testY() time: 170,341,274 ns Round # 5 testZ() time: 170,141,727 ns Round # 5 testV() time: 18,927,686 ns Round # 6 testX() time: 12,680,313 ns Round # 6 testY() time: 170,430,819 ns Round # 6 testZ() time: 170,134,745 ns Round # 6 testV() time: 18,957,994 ns Round # 7 testX() time: 12,640,432 ns Round # 7 testY() time: 170,161,573 ns Round # 7 testZ() time: 170,323,892 ns Round # 7 testV() time: 18,975,037 ns Round # 8 testX() time: 12,688,902 ns Round # 8 testY() time: 170,140,885 ns Round # 8 testZ() time: 170,309,781 ns Round # 8 testV() time: 19,195,668 ns Round # 9 testX() time: 12,670,815 ns Round # 9 testY() time: 170,174,283 ns Round # 9 testZ() time: 170,151,784 ns Round # 9 testV() time: 18,913,577 ns Cumulative time for testX(): 76,012,593 ns Cumulative time for testY(): 1,021,375,960 ns Cumulative time for testZ(): 1,021,176,782 ns Cumulative time for testV(): 113,981,738 ns
![](https://i.iinfo.cz/images/156/javagraph2.png)
Graf porovnávající běh benchmarku v režimu JIT typu client.
Spuštění java -server SynchronizationTest1 (JIT typu server):
Round # 0 testX() time: 33,652,879 ns Round # 0 testY() time: 266,652,180 ns Round # 0 testZ() time: 209,171,145 ns Round # 0 testV() time: 56,007,392 ns Round # 1 testX() time: 1,592,033 ns Round # 1 testY() time: 198,658,486 ns Round # 1 testZ() time: 201,845,495 ns Round # 1 testV() time: 54,062,106 ns Round # 2 testX() time: 1,569,053 ns Round # 2 testY() time: 198,625,726 ns Round # 2 testZ() time: 202,127,167 ns Round # 2 testV() time: 53,543,955 ns Round # 3 testX() time: 1,588,749 ns Round # 3 testY() time: 198,679,778 ns Round # 3 testZ() time: 201,813,998 ns Round # 3 testV() time: 53,580,972 ns Round # 4 testX() time: 1,586,934 ns Round # 4 testY() time: 198,617,617 ns Round # 4 testZ() time: 201,815,044 ns Round # 4 testV() time: 53,593,757 ns Round # 5 testX() time: 1,568,915 ns Round # 5 testY() time: 198,653,379 ns Round # 5 testZ() time: 201,862,810 ns Round # 5 testV() time: 53,540,260 ns Round # 6 testX() time: 1,606,140 ns Round # 6 testY() time: 198,669,451 ns Round # 6 testZ() time: 201,775,362 ns Round # 6 testV() time: 53,868,930 ns Round # 7 testX() time: 1,569,053 ns Round # 7 testY() time: 198,726,169 ns Round # 7 testZ() time: 201,812,234 ns Round # 7 testV() time: 53,610,097 ns Round # 8 testX() time: 1,588,959 ns Round # 8 testY() time: 198,997,647 ns Round # 8 testZ() time: 201,845,334 ns Round # 8 testV() time: 53,576,644 ns Round # 9 testX() time: 1,607,328 ns Round # 9 testY() time: 198,666,182 ns Round # 9 testZ() time: 201,817,256 ns Round # 9 testV() time: 53,561,208 ns Cumulative time for testX(): 9,527,329 ns Cumulative time for testY(): 1,192,330,445 ns Cumulative time for testZ(): 1,210,928,040 ns Cumulative time for testV(): 321,750,896 ns
![](https://i.iinfo.cz/images/150/javagraph3.png)
Graf porovnávající běh benchmarku v režimu JIT typu server.
Co z těchto dat vyplývá? Nejrychlejší (a sémanticky odlišná!) je samozřejmě nesynchronizovaná modifikace nevolatilního atributu, protože zde se může při překladu JIT náležitě věnovat optimalizacím, rozbalovat smyčky atd. atd. Nemusí přitom brát ohled na to, jaké hodnoty atributu uvidí ostatní vlákna. Druhou nejrychlejší metodou je zde použití volatilního atributu, ovšem musíme se smířit s tím, že sémantika je zde odlišná. Rozdíl mezi použitím synchronizovaných metod a synchronizovaných bloků je viditelný pouze v režimu interpretru, což ale dává smysl, protože zde musí interpret správně vyhodnotit instrukce monitorenter a monitorexit, zatímco synchronizované metody implicitně obsahují zámek přímo na svém vstupu.
9. 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ého jednoduchého benchmarku:
# | Zdrojový soubor/skript | Umístění souboru v repositáři |
---|---|---|
1 | SynchronizationTest1.java | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/eceb6e722485/sync/SynchronizationTest1.java |
2 | run.sh | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/eceb6e722485/sync/run.sh |
10. 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