2013-04-02 13 views
27

Podczas niedawnej dyskusji na temat optymalizacji kodu, powiedziano mi, że zerwanie kodu na wiele małych metod może znacznie zwiększyć wydajność, ponieważ kompilator JIT nie lubi optymalizować dużych metod.Czy to prawda, że ​​posiadanie wielu małych metod pomaga zoptymalizować kompilator JIT?

Nie byłem tego pewien, ponieważ wydaje się, że kompilator JIT powinien sam być w stanie zidentyfikować niezależne segmenty kodu, niezależnie od tego, czy są one we własnej metodzie, czy nie.

Czy ktoś może potwierdzić lub odrzucić to roszczenie?

+0

Ogólny proces kompilacji JIT składa się z następujących kroków ... http: //publib.boulder.ibm.com/infocenter/java7sdk/v7r0/index.jsp? Topic =% 2Fcom.ibm.java.win.70.doc % 2Fdiag% 2Foundstanding% 2Fjit_overview.html, ale nie mówi o tym, jak jit obsługuje duże lub małe moduły. – AurA

Odpowiedz

1

Naprawdę nie rozumiem, jak to działa, ale na podstawie the link AurA provided, zgaduję, że kompilator JIT będzie musiał skompilować mniej bajtów, jeśli te same bity są ponownie wykorzystywane, zamiast kompilować inny kod bajtowy, który jest podobny w różnych metodach.

Oprócz tego, im więcej jesteś w stanie rozbić swój kod na kawałki zmysłów, tym więcej wykorzystasz swojego kodu i to jest coś, co pozwoli na optymalizację maszyny wirtualnej (ty zapewniają więcej schematu do pracy).

Jednak wątpię, czy będzie to miało jakikolwiek pozytywny wpływ, jeśli złamiesz swój kod bez żadnego sensu, który nie zapewnia ponownego użycia kodu.

7

Jeśli weźmiesz ten sam kod i podzielisz go na wiele małych metod, to w ogóle nie pomoże to JIT.

Lepszym rozwiązaniem jest to, że nowoczesne maszyny wirtualne HotSpot JVM nie penalizują za pisanie wielu małych metod. Są agresywnie zorientowani, więc w czasie wykonywania programu nie ponosisz kosztów połączeń. Dotyczy to nawet wywołań wirtualnych, na przykład wywołujących metodę interfejsu.

Zrobiłem blog post kilka lat temu, który opisuje, w jaki sposób można zobaczyć JVM metody inline. Ta technika nadal ma zastosowanie w nowoczesnych maszynach JVM. Odkryłem również, że warto przyjrzeć się dyskusjom dotyczącym invokedynamic, w jaki sposób obszerne omówienie współczesnych maszyn wirtualnych HotSpot JVM kompiluje kod bajtowy Java.

+0

"* Jeśli weźmiesz ten sam kod i podzielisz go na wiele małych metod, to nie pomoże to JIT w ogóle. * "=> Nie sądzę, że to jest dokładne (przynajmniej w hotspocie). – assylias

+0

Powód, dla którego mówię, że jest w zasadzie taki sam jak to, co napisał @Raewald w innej odpowiedzi, cytuję: "ale jeśli metody, które on proponuje, istnieją tylko dlatego, że duża metoda została podzielona na kilka metod, nic nie zostało zdobyte". Czy wyjaśniłbyś, dlaczego uważasz, że nie jest to dokładne? –

+0

Zobacz podany przykład - z jedną dużą metodą: bez inliningu, z dwoma mniejszymi metodami robiącymi dokładnie to samo, obaj zostają zainicjowani. – assylias

19

JIT typu Hotspot wprowadza tylko metody, których rozmiar jest mniejszy niż określony (konfigurowalny). Tak więc użycie mniejszych metod pozwala na bardziej inlineing, co jest dobre.

Zobacz różne opcje inline na this page.


EDIT

Aby rozwinąć trochę:

  • jeśli metoda jest mały, zostanie on inlined więc jest mała szansa, aby uzyskać ukarany za podzielenie kodu w małych sposobów.
  • w niektórych przypadkach metody dzielenia mogą powodować więcej wstawiania.

Przykład (pełny kod mają te same numery linii, jeśli spróbujesz go)

package javaapplication27; 

public class TestInline { 
    private int count = 0; 

    public static void main(String[] args) throws Exception { 
     TestInline t = new TestInline(); 
     int sum = 0; 
     for (int i = 0; i < 1000000; i++) { 
      sum += t.m(); 
     } 
     System.out.println(sum); 
    } 

    public int m() { 
     int i = count; 
     if (i % 10 == 0) { 
      i += 1; 
     } else if (i % 10 == 1) { 
      i += 2; 
     } else if (i % 10 == 2) { 
      i += 3; 
     } 
     i += count; 
     i *= count; 
     i++; 
     return i; 
    } 
} 

Po uruchomieniu tego kodu z następującymi flagami JVM: -XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:FreqInlineSize=50 -XX:MaxInlineSize=50 -XX:+PrintInlining (tak mam używane wartości to udowodnić moją Sprawa: m jest za duża, ale zarówno refaktoryzowane m, jak i m2 są poniżej progu - z innymi wartościami możesz otrzymać inne wyniki.

Zobaczysz, że m() i main() uzyskać skompilowany, ale m() nie zostanie inlined:

56 1    javaapplication27.TestInline::m (62 bytes) 
57 1 %   javaapplication27.TestInline::main @ 12 (53 bytes) 
      @ 20 javaapplication27.TestInline::m (62 bytes) too big 

Można również sprawdzić wygenerowany montaż potwierdzić, że m nie inlined (użyłem te flagi JVM: -XX:+PrintAssembly -XX:PrintAssemblyOptions=intel) - będzie to wyglądać tak:

0x0000000002780624: int3 ;*invokevirtual m 
          ; - javaapplication27.TestInline::[email protected] (line 10) 

Jeśli byłaby kodu takiego (mam ekstrakcji if/else w osobnej metody):

public int m() { 
    int i = count; 
    i = m2(i); 
    i += count; 
    i *= count; 
    i++; 
    return i; 
} 

public int m2(int i) { 
    if (i % 10 == 0) { 
     i += 1; 
    } else if (i % 10 == 1) { 
     i += 2; 
    } else if (i % 10 == 2) { 
     i += 3; 
    } 
    return i; 
} 

Zobaczysz następujące czynności kompilacji:

60 1    javaapplication27.TestInline::m (30 bytes) 
60 2    javaapplication27.TestInline::m2 (40 bytes) 
      @ 7 javaapplication27.TestInline::m2 (40 bytes) inline (hot) 
63 1 %   javaapplication27.TestInline::main @ 12 (53 bytes) 
      @ 20 javaapplication27.TestInline::m (30 bytes) inline (hot) 
      @ 7 javaapplication27.TestInline::m2 (40 bytes) inline (hot) 

Więc m2 dostaje inlined do m, których można się spodziewać, więc jesteśmy z powrotem do oryginalnego scenariusza. Ale kiedy kompiluje się main, to faktycznie dodaje do niej całą treść. Na poziomie zespołu oznacza to, że nie znajdziesz już żadnych instrukcji dotyczących invokevirtual. Znajdziesz linie takie jak ta:

0x00000000026d0121: add ecx,edi ;*iinc 
             ; - javaapplication27.TestInline::[email protected] (line 33) 
             ; - javaapplication27.TestInline::[email protected] (line 24) 
             ; - javaapplication27.TestInline::[email protected] (line 10) 

, gdzie zasadniczo wspólne instrukcje są "wspólne".

Wnioski

Nie jestem mówiąc, że ten przykład jest reprezentatywny, ale wydaje się udowodnić kilka punktów:

  • stosując mniejszą sposób poprawia czytelność w kodzie
  • mniejsze metody będą ogólnie być zainicjowanym, więc najprawdopodobniej nie zapłacisz kosztu dodatkowej metody wywołania (będzie to neutralne pod względem wydajności).
  • przy użyciu mniejszych metod może poprawić inline globalnie w pewnych okolicznościach, jak pokazano na przykładzie powyżej

i wreszcie: jeśli część kodu jest bardzo krytyczny dla wydajności, że te rozważania znaczenia, należy zbadać wyjście JIT do dostrojenia Twój kod i ważny profil przed i po.

+1

Odpowiednią opcją jest '-XX: InlineSmallCode = n' – Raedwald

+0

" Używanie mniejszych metod pozwala na większą inlineing, co jest dobre ", ale jeśli metody, które on narysuje istnieją tylko dlatego, że duża metoda została podzielona na kilka metod, nic nie ma uzyskano. – Raedwald

+1

@Raedwald 'FreqInlineSize' i' MaxInlineSize' są również istotne w zależności od tego, co próbujesz osiągnąć. Odnośnie twojego drugiego komentarza: nie jest to aż tak jednoznaczne - jeśli 'm1()' wywołuje 'm2()' i oba są w granicach ograniczających, cały "m1 + m2" będzie prawdopodobnie wstawiony w stronę wywołującą (jeśli nazywa się dość często itd.). Jeśli z drugiej strony scalisz dwie metody w 'm()', która jest dłuższa niż limit, nic nie zostanie wbudowane i stracisz tę optymalizację. Postaram się dodać przykład później. – assylias

Powiązane problemy