Na czym polega technika inwersji pętli?

Przeglądałem dokument, który mówi o just-In-time compiler (JIT) technikach optymalizacji dla Javy. Jednym z nich była "inwersja pętli". A dokument mówi:

Zamieniasz zwykłą pętlę {[0] } Na pętlę do-while. Oraz Pętla do-while jest ustawiona w klauzuli if. Wymiana ta prowadzi do dwóch mniej skoków.

Jak działa inwersja pętli i jak optymalizuje ścieżkę kodu?

N. B.: byłoby świetnie, gdyby ktoś może wyjaśnić na przykładzie kodu Javy i jak JIT optymalizuje go do kodu natywnego i dlaczego jest optymalny w nowoczesnych procesorach.

Author: Trying, 2013-12-29

4 answers

while (condition) { 
  ... 
}

Workflow:

  1. check condition;
  2. if false, jump to outside of loop;
  3. Uruchom jedną iterację;
  4. Skocz na górę.

if (condition) do {
  ...
} while (condition);

Workflow:

  1. check condition;
  2. if false, jump to beyond the loop;
  3. Uruchom jedną iterację;
  4. check condition;
  5. jeśli prawda, przejdź do kroku 3.

Porównując te dwa można łatwo zauważyć, że te ostatnie mogą w ogóle nie wykonywać żadnych skoków, pod warunkiem, że jest dokładnie jeden krok przez pętlę, a ogólnie liczba skoków będzie o jeden mniej niż liczba iteracji. Pierwszy będzie musiał wrócić, aby sprawdzić warunek, tylko wyskoczyć z pętli, gdy warunek jest fałszywy.

Skoki na nowoczesnych architekturach CPU z procesorami mogą być dość kosztowne: ponieważ procesor kończy wykonywanie kontroli przed skokiem, instrukcje poza tym skokiem są już w środku potoku. Całe to przetwarzanie musi zostać odrzucone, jeśli / align = "left" / Dalsze wykonanie jest opóźnione, gdy rurociąg jest reprymendowany.

Wyjaśnienie wspomnianej gałęzi przewidywanie gałęzi : dla każdego rodzaju skoku warunkowego procesor ma dwie instrukcje, każda zawierająca zakład na wynik. Na przykład na końcu pętli umieścisz instrukcję "jump if not zero, betting on not zero", ponieważ skok będzie musiał zostać wykonany na wszystkich iteracjach z wyjątkiem ostatniej. Tędy. procesor zaczyna pompować swój rurociąg z instrukcjami podążającymi za celem skoku, a nie tymi, które podążają za samą instrukcją skoku.

Ważna uwaga

Proszę zrobić nie weź to jako przykład jak zoptymalizować na poziomie kodu źródłowego. Byłoby to całkowicie błędne, ponieważ, jak już wynika z twojego pytania, transformacja z pierwszej formy do drugiej jest czymś, co kompilator JIT robi rutynowo, całkowicie sam.

 106
Author: Marko Topolnik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-29 22:12:05

Może to zoptymalizować pętlę, która jest zawsze wykonywana co najmniej raz.

Zwykła pętla while będzie wtedy zawsze skakać z powrotem do początku przynajmniej raz i skakać do końca raz na końcu. Przykład prostej pętli uruchamianej raz:

int i = 0;
while (i++ < 1) {
    //do something
}  

A do-while pętla z drugiej strony pominie pierwszy i ostatni skok. Poniżej znajduje się pętla równoważna do powyższej, która będzie działać bez skoków:

int i = 0;
if (i++ < 1) {
    do {
        //do something
    } while (i++ < 1); 
}
 23
Author: Keppil,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-29 15:45:10

Przejdźmy przez nie:

Wersja while:

void foo(int n) {
    while (n < 10) {
       use(n);
       ++n;
    }
    done();
}
  1. najpierw testujemy n i przeskakujemy do done();, jeśli warunek nie jest prawdziwy.
  2. następnie używamy i zwiększamy n.
  3. Teraz wracamy do stanu.
  4. przepłukać, powtórzyć.
  5. gdy warunek nie jest już prawdziwy, przeskakujemy do done().

Wersja do-while:

(pamiętaj, że nie robimy tego w kodzie źródłowym [co wprowadziłoby konserwację problemy], kompilator/JIT robi to za nas.)

void foo(int n) {
    if (n < 10) {
        do {
            use(n);
            ++n;
        }
        while (n < 10);
    }
    done();
}
  1. najpierw testujemy n i przeskakujemy do done();, jeśli warunek nie jest prawdziwy.
  2. następnie używamy i zwiększamy n.
  3. teraz testujemy warunek i skaczemy z powrotem, jeśli to prawda.
  4. przepłukać, powtórzyć.
  5. gdy warunek nie jest już prawdziwy, płyniemy (nie przeskakujemy) do done().

Więc na przykład, jeśli n zaczyna się 9, nigdy nie przeskakujemy w wersji do-while, natomiast w wersji while wersja musimy wrócić do początku, zrobić test, a następnie przejść z powrotem do końca, gdy widzimy, że to nieprawda.

 3
Author: T.J. Crowder,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-29 15:38:28

Inwersja pętli jest techniką optymalizacji wydajności, która poprawia wydajność, ponieważ procesor może osiągnąć ten sam wynik przy mniejszej liczbie instrukcji. Powinno to przede wszystkim poprawić wydajność w warunkach brzegowych.

Ten link stanowi kolejny przykład inwersji pętli. W kilku architekturach, w których decrement i compare są zaimplementowane jako pojedynczy zestaw instrukcji, warto przekonwertować pętlę for NA A while z decrement i compare operacja.

Wikipedia ma bardzo dobry przykład i wyjaśniam go tutaj ponownie.

 int i, a[100];
  i = 0;
  while (i < 100) {
    a[i] = 0;
    i++;
  }

Zostanie przekonwertowany przez kompilator na

  int i, a[100];
  i = 0;
  if (i < 100) {
    do {
      a[i] = 0;
      i++;
    } while (i < 100);
  }

Jak to przekłada się na wydajność? Gdy wartość i wynosi 99, procesor nie musi wykonywać GOTO (co jest wymagane w pierwszym przypadku). Poprawia to wydajność.

 3
Author: Anirudhan J,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-29 15:44:28