Hlavní navigace

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

8. 10. 2013
Doba čtení: 19 minut

Sdílet

V dnešní části seriálu o programovacím jazyce Java i o virtuálním stroji Javy si nejdříve řekneme, jak vyřešili autoři HotSpotu problematiku tvorby safe-pointů. Posléze se seznámíme s principem generování safe-pointů na platformě x86 a dále se budeme zabývat problematikou synchronizovaných metod a bloků.

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

4. Ukázkový příklad

5. Synchronizované metody a synchronizované bloky

6. Demonstrační benchmark SynchronizationTest1

7. Bajtkód testovacích metod

8. Výsledky spuštění benchmarku

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

10. 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 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í tableswitchlookupswitch, 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,Bcmp 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()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()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()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()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()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í monitorentermonitorexit, 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

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

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

Graf porovnávající běh benchmarku v režimu JIT typu server.

CS24_early

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 monitorentermonitorexit, 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:

10. 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ý?

Autor článku

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