Dlaczego w wielu kompilatorach wykorzystywanych w branży preferowane jest przypisanie statycznego pojedynczego nad stylem przekazywania ciągłego?


16

Według strony Wikipedii na temat statycznego pojedynczego przypisania (SSA) , SSA jest używany przez duże i dobrze znane projekty, takie jak LLVM, GCC, MSVC, Mono, Dalvik, SpiderMonkey i V8, podczas gdy strona o projektach używa stylu kontynuacji przejścia (CPS) jest trochę brakuje w porównaniu.

Mam pojęcie, że CPS jest preferowany przez kompilatory i interpretatory, które implementują przede wszystkim języki funkcjonalne - w szczególności Haskell i Scheme wydają się mieć silne skłonności do stylu CPS ze względu na ograniczenia mutacji lub potrzebę pierwszej klasy kontynuacji wsparcia (sądzę, że że Smalltalk również tego potrzebuje). Główną literaturą, z którą się spotkałem, która używa CPS, wydają się być te, które przede wszystkim pracują nad Schematem lub pod pewnym względem są związane ze Schematem.

Czy istnieje jakiś szczególny powód, dla którego SSA jest stosowany w przemyśle oprócz tempa adopcji? SSA i CPS mają bliski związek; oznacza to, że łatwo byłoby stwierdzić inaczej, ale być może reprezentacja informacji byłaby mniej zwarta lub mniej wydajna dla CPS.


3
IIRC, tradycyjne optymalizacje dla imperatywnych języków zaprojektowane w czasach tradycyjnej analizy przepływu danych przesyłane do postaci SSA łatwiej niż do CPS. W szczególności łańcuchy use-def i def-use można „odczytać” bezpośrednio z reprezentacji SSA. Bezwładność się liczy
pseudonim

Odpowiedzi:


9

Wyobrażam sobie, że wielu implementatorów kompilatorów dla typowych języków imperatywnych po prostu nie znało technik kompilacji CPS i opartych na CPS. W społeczności programowania funkcjonalnego zarówno CPS, jak i kompilacja oparta na CPS są bardzo dobrze znanymi technikami - ta ostatnia pochodzi z pracy Guy Steele. Niemniej jednak, nawet w społeczności FP, większość kompilatorów nie używa technik kompilacji opartych na CPS, chyba że sam język obsługuje takie operatory sterowania call/cc. Stosuje się coś bardziej podobnego do normalnej formy administracyjnej (ANF) (czasami nazywanej również normalną formą monadyczną, która jest ściśle powiązana z przyczyn, które staną się jasne), która ma jeszcze ściślejszy związek z SSA niż CPS .

O ile dobrze pamiętam, normalna forma administracyjna bierze swoją nazwę od faktu, że kompilacja oparta na CPS może prowadzić do beta-redeksów w kodzie pośrednim, które nie odpowiadają żadnej części w kodzie źródłowym. Były one określane jako „redeksy administracyjne”. Mogły one ulec skróceniu w czasie kompilacji, ale przeprowadzono wiele badań nad przeprowadzeniem transformacji CPS, która wygenerowałaby kod bez administracyjnych powtórzeń. Następnie celem było uzyskanie wyników w normalnej formie, w której wszystkie „administracyjne” redeksy zostały zredukowane, i to było początkiem normalnej formy administracyjnej. Nie trzeba było długo czekać, aby zrozumieć, że nie było wiele korzyści z postrzegania tego jako (n optymalizacja) dwuetapowego procesu: transformacja CPS, redukcja redeksów administracyjnych. W szczególności, normalna forma administracyjna wygląda raczej jak styl monadyczny, jak praktykowana (ręcznie), szczególnie w Haskell. Transformację CPS można rozumieć jako konwersję do stylu monadycznego, w którym akurat używasz monady CPS (więc istnieje naprawdę wiele sposobów na „konwersję” do stylu monadycznego odpowiadających różnym porządkom oceny). Ogólnie rzecz biorąc, możesz używać zupełnie innej monady, więc konwersja do stylu monadycznego, a zatem normalna forma administracyjna, nie jest tak naprawdę szczególnie związana z CPS.

Niemniej jednak CPS w porównaniu z ANF miał pewne zalety. W szczególności istniały pewne optymalizacje, które można wykonać w CPS za pomocą standardowych optymalizacji, takich jak redukcja wersji beta, które wymagały (pozornie) reguł ad-hoc dla ANF. Z perspektywy monadycznej reguły te odpowiadają konwersjom dojazdów do pracy. Rezultatem jest teraz teoria, która może wyjaśnić, jakie reguły należy dodać i dlaczego. Ostatni papier daje (nowy) i dość dokładny opis tego (z logicznego punktu widzenia) i jego pokrewne sekcja praca służy jako krótki, ale godnej badania i odniesienia do literatury na tematy wspominam.

Problem z CPS wiąże się z jedną z jego głównych zalet. Transformacja CPS pozwala na implementację takich operatorów sterowania call/cc, ale oznacza to, że każde nielokalne wywołanie funkcji w kodzie pośrednim CPS musi być traktowane jako potencjalnie wykonujące efekty sterowania. Jeśli twój język zawiera operatory sterowania, to jest tak, jak powinno być (chociaż nawet wtedy większość funkcji prawdopodobnie nie wykonuje żadnych kontroli shenanigans). Jeśli twój język nie obejmuje operatorów sterujących, istnieją globalne niezmienniki dotyczące użycia kontynuacji, które nie są widoczne lokalnie. Oznacza to, że istnieją optymalizacje, których nie można wykonać na ogólnym kodzie CPS, których wykonanie przy szczególnie dobrze zachowanym użyciu CPS byłoby rozsądne. Jednym ze sposobów jest tozmiana precyzji analiz przepływu danych i kontroli . (Transformacja CPS pomaga w pewien sposób, boli w innych, chociaż sposoby, w które pomaga, są głównie spowodowane duplikacją, a nie samym aspektem CPS.) 1 Możesz oczywiście dodać reguły i dostosować analizy, aby to zrekompensować (tj. Wykorzystać globalne niezmienniki), ale częściowo pokonałeś jedną z głównych zalet kompilacji opartej na CPS, a mianowicie to, że wiele (pozornie) specjalnych optymalizacji ad-hoc staje się specjalnymi przypadkami optymalizacji ogólnego przeznaczenia (szczególnie redukcji beta ).

Ostatecznie, chyba że twój język ma operatorów sterujących, zwykle nie ma zbyt wiele powodów, aby używać schematu kompilacji opartego na CPS. Po zrekompensowaniu problemów, o których wspomniałem powyżej, zazwyczaj eliminujesz zalety kompilacji opartej na CPS i tworzysz coś równoważnego z nieużywaniem CPS. W tym momencie CPS tworzy zawiły wygląd kodu pośredniego, który nie przynosi większych korzyści. Argument za kompilacją opartą na CPS z 2007 roku rozwiązuje niektóre z tych problemów i przedstawia inne korzyści przy użyciu innej formy konwersji CPS. To, co porusza papier, jest częściowo ujęte w artykule (2017) , o którym wspominałem wcześniej.

1 Czy równoważność między SSA i CPS nie czyni tego mniej lub bardziej niemożliwym? Nie. Jedną z pierwszych rzeczy, która mówi o wprowadzeniu tego równoważnika , jest to, że równoważność nie działa dla dowolnego kodu CPS, ale działa na wyjściu transformacji CPS (którą definiują) dla języka bez operatorów sterujących.


2
Minęło trochę czasu od pierwszej odpowiedzi, ale czuję się wystarczająco dobrze, aby w końcu zaakceptować odpowiedź, ponieważ rozumiem więcej niż 10% ...
CinchBlue

2

Nie jestem ekspertem od kompilatorów, więc weź to, co mówię z odrobiną soli, mam nadzieję, że zagra prawdziwy ekspert od kompilatorów. Powiedziano mi, że:

  • Kod CPSed często przeskakuje nielokalnie, co niszczy lokalizację pamięci podręcznej, a tym samym wydajność.
  • CPS wymaga droższego wyrzucania elementów bezużytecznych niż konwencjonalna kompilacja, która zwykle może przechowywać wiele rzeczy na stosie (co jest szybsze w przydzielaniu i zwalnianiu).

Oba spiskują, aby kod kompilowany przez transformację CPS był stosunkowo wolny.


3
Myślę, że miksujesz przekształcony kod źródłowy CPS (lub transformację między źródłami) z użyciem CPS jako języka pośredniego . Zakładając, że Twój język wejściowy nie obsługuje efektów kontrolnych, takich jak call/cc, na przykład, kontynuacje będą używane z dyscypliną stosu - nie będzie potrzebny żaden moduł wyrzucania elementów bezużytecznych, a kontynuacje będą odpowiadały mniej więcej krawędziom kontroli wykres, więc nie będzie żadnych dodatkowych skoków. Nie trzeba implementować kontynuacji za pomocą ogólnej implementacji funkcji wyższego rzędu.
Derek Elkins opuścił SE

@DerekElkins Nie było dla mnie jasne, o co prosi OP.
Martin Berger,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.