Śledzenie kompilacji just-in-time

Śledzenie kompilacji just-in-time to technika używana przez maszyny wirtualne do optymalizacji wykonywania programu w czasie wykonywania . Odbywa się to poprzez rejestrację liniowej sekwencji często wykonywanych operacji, kompilację ich do natywnego kodu maszynowego i wykonanie. Jest to przeciwieństwo tradycyjnych just-in-time (JIT), które działają na podstawie metody.

Przegląd

Kompilacja just-in-time to technika zwiększania szybkości wykonywania programów poprzez kompilowanie części programu do kodu maszynowego w czasie wykonywania. Jednym ze sposobów kategoryzowania różnych kompilatorów JIT jest ich zakres kompilacji. Podczas gdy kompilatory JIT oparte na metodach tłumaczą jedną metodę na kod maszynowy, śledzenie JIT wykorzystuje często wykonywane pętle jako jednostkę kompilacji. Śledzenie JIT opiera się na założeniu, że programy spędzają większość czasu w niektórych pętlach programu („hot loops”), a kolejne iteracje pętli często przebiegają podobnymi ścieżkami. Wirtualne maszyny które mają śledzący JIT, są często środowiskami wykonawczymi w trybie mieszanym, co oznacza, że ​​oprócz śledzącego JIT mają albo interpreter, albo kompilator metod.

Szczegóły techniczne

Śledzący kompilator JIT przechodzi przez różne fazy w czasie wykonywania. Najpierw zbierane są informacje o profilowaniu dla pętli. Po zidentyfikowaniu gorącej pętli następuje specjalna faza śledzenia , w której rejestrowane są wszystkie wykonane operacje tej pętli. Ta sekwencja operacji nazywana jest śladem. Śledzenie jest następnie optymalizowane i kompilowane do kodu maszynowego (śledzenie). Kiedy ta pętla jest wykonywana ponownie, zamiast odpowiednika programu wywoływany jest skompilowany ślad.

Kroki te zostały szczegółowo wyjaśnione poniżej:

Faza profilowania

Celem profilowania jest identyfikacja gorących pętli. Często odbywa się to poprzez zliczanie liczby iteracji dla każdej pętli. Gdy liczba pętli przekroczy określony próg, pętla jest uważana za gorącą i rozpoczyna się faza śledzenia.

Faza śledzenia

W fazie śledzenia wykonanie pętli przebiega normalnie, ale dodatkowo każda wykonana operacja jest zapisywana w śladzie. Zarejestrowane operacje są zwykle przechowywane w drzewie śledzenia , często w reprezentacji pośredniej (IR). Śledzenie następuje po wywołaniach funkcji, co prowadzi do ich wstawienia do śladu. Śledzenie jest kontynuowane, dopóki pętla nie dotrze do końca i nie przeskoczy z powrotem na początek.

Ponieważ ślad jest rejestrowany przez podążanie jedną konkretną ścieżką wykonania pętli, późniejsze wykonania tego śladu mogą odbiegać od tej ścieżki. Aby zidentyfikować miejsca, w których może się to zdarzyć, do śladu wstawiane są specjalne instrukcje strażników . Jednym z przykładów takiego miejsca są instrukcje if. Strażnik to szybkie sprawdzenie, czy pierwotny stan jest nadal prawdziwy. Jeśli strażnik zawiedzie, wykonanie śledzenia zostanie przerwane.

Ponieważ śledzenie odbywa się podczas wykonywania, śledzenie może zawierać informacje o czasie wykonywania (np. informacje o typie ). Informacje te można później wykorzystać w fazie optymalizacji w celu zwiększenia wydajności kodu.

Faza optymalizacji i generowania kodu

Ślady są łatwe do optymalizacji, ponieważ reprezentują tylko jedną ścieżkę wykonania, co oznacza, że ​​nie istnieje żaden przepływ sterowania i nie wymaga obsługi. Typowe optymalizacje obejmują eliminację stałych podwyrażeń , eliminację martwego kodu , alokację rejestrów , ruch niezmiennego kodu , ciągłe składanie i analizę ucieczki .

Po optymalizacji ślad jest zamieniany na kod maszynowy. Podobnie jak optymalizacja, jest to łatwe ze względu na liniowy charakter śladów.

Wykonanie

Po skompilowaniu śladu do kodu maszynowego można go wykonać w kolejnych iteracjach pętli. Wykonywanie śledzenia jest kontynuowane do momentu awarii zabezpieczenia.

Historia

Podczas gdy idea JIT sięga lat 60. XX wieku, JIT-y śledzące zaczęto stosować częściej dopiero od niedawna. Pierwsza wzmianka o pomyśle podobnym do dzisiejszego pomysłu śledzenia JIT pojawiła się w 1970 roku. Zaobserwowano, że skompilowany kod można uzyskać z interpretera w czasie wykonywania, po prostu zapisując działania wykonane podczas interpretacji.

Pierwszą implementacją śledzenia jest Dynamo, „system dynamicznej optymalizacji oprogramowania, który jest w stanie w przejrzysty sposób poprawić wydajność natywnego strumienia instrukcji wykonywanych na procesorze”. W tym celu strumień instrukcji natywnych jest interpretowany do momentu znalezienia „gorącej” sekwencji instrukcji. Dla tej sekwencji generowana jest zoptymalizowana wersja, zapisywana w pamięci podręcznej i wykonywana.

Dynamo zostało później rozszerzone na DynamoRIO . Jeden z projektów opartych na DynamoRIO był platformą konstrukcyjną interpretera, która łączy śledzenie i częściową ocenę. Został użyty do „dynamicznego usuwania narzutu tłumacza z implementacji językowych”.

opracowano HotpathVM, pierwszy śledzący kompilator JIT dla języka wysokiego poziomu [ potrzebne źródło ] . Ta maszyna wirtualna była w stanie dynamicznie identyfikować często wykonywane instrukcje kodu bajtowego, które są śledzone, a następnie kompilowane do kodu maszynowego przy użyciu konstrukcji statycznego pojedynczego przypisania (SSA). Motywacją dla HotpathVM było posiadanie wydajnej maszyny JVM dla urządzeń mobilnych o ograniczonych zasobach.

Innym przykładem śledzenia JIT jest TraceMonkey , jedna z implementacji JavaScript Mozilli dla Firefoksa ( 2009). TraceMonkey kompiluje często wykonywane ślady pętli w dynamicznym języku JavaScript w czasie wykonywania i specjalizuje się w wygenerowanym kodzie dla rzeczywistych typów dynamicznych występujących na każdej ścieżce.

Innym projektem wykorzystującym śledzenie JIT jest PyPy . Umożliwia wykorzystanie śledzenia JIT dla implementacji językowych, które zostały napisane za pomocą łańcucha narzędzi tłumaczeniowych PyPy, poprawiając w ten sposób wydajność każdego programu wykonywanego przy użyciu tego tłumacza. Jest to możliwe dzięki śledzeniu samego interpretera, a nie programu wykonywanego przez tłumacza.

Śledzenie JIT zostało również zbadane przez Microsoft w ramach projektu SPUR dla ich wspólnego języka pośredniego (CIL). SPUR to ogólny moduł śledzący dla CIL, którego można również używać do śledzenia za pomocą implementacji JavaScript.

Przykład śladu

Rozważmy następujący program w Pythonie, który oblicza sumę kwadratów kolejnych liczb całkowitych, aż ta suma przekroczy 100 000:

 
       

  0
  0
 
      
       
        
         def  kwadrat  (  x  ):  powrót  x  *  x  i  =  y  =  podczas gdy  Prawda  :  y  +=  kwadrat  (  i  )  jeśli  y  >  100000  :  przerwa  i  =  i  +  1 

Ślad dla tego programu może wyglądać mniej więcej tak:

  
    		
    		
    
 
    		
  start w pętli  (  i1  ,  y1  )  i2  =  int_mul  (  i1  ,  i1  )  # x*x  y2  =  int_add  (  y1  ,  i2  )  # y += i*i  b1  =  int_gt  (  y2  ,  100000  )  guard_false  (  b1  )  i3  =  int_add  (  i1  ,  1  )  # ja = i+1  skok  (   i3  ,  y2  ) 

Zwróć uwagę, jak wywołanie funkcji do kwadratu jest wstawiane do śladu i jak instrukcja if jest zamieniana na guard_false .

Zobacz też

Linki zewnętrzne