Perché l'assegnazione statica-singola è preferita rispetto allo stile di passaggio di continuazione in molti compilatori utilizzati nel settore?


16

Secondo la pagina di Wikipedia sull'assegnazione statica-singola (SSA) , SSA viene utilizzata da progetti di grandi dimensioni e ben noti come LLVM, GCC, MSVC, Mono, Dalvik, SpiderMonkey e V8 mentre la pagina su progetti utilizza lo stile di passaggio di continuazione (CPS) è un po 'carente in confronto.

Ho questa idea che CPS sia preferito da compilatori e interpreti che implementano principalmente linguaggi funzionali - in particolare, Haskell e Scheme sembrano avere forti inclinazioni verso lo stile CPS a causa delle restrizioni sulla mutazione o della necessità di un supporto di continuità di prima classe (immagino che Smalltalk avrebbe probabilmente bisogno anche di questo). La letteratura principale che ho riscontrato che utilizza CPS sembra essere quella che sta lavorando principalmente su Scheme o è correlata a Scheme in qualche modo.

C'è qualche motivo particolare per cui SSA è utilizzato nell'industria oltre allo slancio di adozione? SSA e CPS hanno una relazione stretta; ciò significa che sarebbe facile affermare in termini di un altro, ma forse la rappresentazione delle informazioni sarebbe meno compatta o meno efficiente per CPS.


2
IIRC, ottimizzazioni tradizionali per linguaggi imperativi progettati ai tempi del tradizionale trasferimento dell'analisi del flusso di dati in SSA si formano più facilmente di quanto non facciano in CPS. In particolare, le catene use-def e def-use possono "leggere" direttamente la rappresentazione SSA. L'inerzia conta qualcosa
pseudonimo del

Risposte:


9

Immagino che molti implementatori di compilatori per linguaggi imperativi tipici non conoscessero semplicemente le tecniche di compilazione basate su CPS e CPS. Nella comunità di programmazione funzionale sia la compilazione basata su CPS che quella basata su CPS sono tecniche molto conosciute, quest'ultima dal lavoro di Guy Steele. Tuttavia, anche nella comunità FP, la maggior parte dei compilatori non utilizza tecniche basate su CPS per la compilazione a meno che il linguaggio stesso non supporti operatori di controllo come call/cc. Viene utilizzato qualcosa di più simile al modulo normale amministrativo (ANF) (a volte indicato anche come modulo normale monadico che è intimamente correlato per ragioni che risulteranno chiare) che ha una relazione ancora più stretta con SSA rispetto a CPS .

Se ricordo bene, il modulo amministrativo normale prende il nome dal fatto che la compilazione basata su CPS può portare a beta-redexes nel codice intermedio che non corrispondono a nulla nel codice sorgente. Questi sono stati denominati "redexes amministrativi". Questi potrebbero ridursi in fase di compilazione, ma c'era una buona quantità di ricerca sull'esecuzione di una trasformazione CPS che avrebbe prodotto il codice senza i redexes amministrativi in ​​primo luogo. L'obiettivo era quindi quello di produrre output in una forma normale in cui tutti i redex "amministrativi" erano ridotti e questa era l'origine della forma normale amministrativa. Non ci è voluto molto tempo per rendersi conto che non ci sono stati molti vantaggi nel vederlo come un (n ottimizzazione di un) processo in due fasi: trasformazione CPS, riduzione dei redexes amministrativi. In particolare, la forma normale amministrativa assomiglia piuttosto allo stile monadico praticato (a mano) in particolare a Haskell. La trasformazione CPS può essere intesa come una conversione in stile monadico in cui ti capita di usare la monade CPS (quindi ci sono davvero molti modi per "convertire" in stile monadico corrispondente a diversi ordini di valutazione). In generale, tuttavia, potresti usare una monade abbastanza diversa, quindi la conversione in stile monadico e quindi in una forma normale amministrativa non è in realtà correlata al CPS in particolare.

Tuttavia, ci sono stati alcuni vantaggi in CPS rispetto a ANF. In particolare, c'erano alcune ottimizzazioni che potevi fare in CPS con solo ottimizzazioni standard, come la riduzione beta, che richiedevano (apparentemente) regole ad hoc per ANF. Dal punto di vista monadico, queste regole corrispondono alle conversioni pendolari. Il risultato è che ora esiste una teoria che può spiegare quali regole dovrebbero essere aggiunte e perché. Un recente articolo fornisce una (nuova e) descrizione abbastanza chiara di ciò (da una prospettiva logica) e la relativa sezione di lavoro funge da breve ma decente indagine e riferimenti alla letteratura sugli argomenti che menziono.

Il problema con CPS è legato a uno dei suoi principali vantaggi. La trasformazione CPS consente di implementare operatori di controllo come call/cc, ma ciò significa che ogni chiamata di funzione non locale nel codice intermedio CPS deve essere trattata come effetti di controllo potenzialmente performanti. Se la tua lingua include operatori di controllo, allora è esattamente come dovrebbe essere (sebbene anche allora la maggior parte delle funzioni probabilmente non eseguono shenanigans di controllo). Se la tua lingua non include gli operatori di controllo, allora ci sono invarianti globali sull'uso di continuazioni che non sono evidenti localmente. Ciò significa che ci sono ottimizzazioni che non sono corrette da eseguire sul codice CPS generale e che sarebbe opportuno eseguire su questo uso particolarmente ben educato del CPS. In questo modo si manifestail cambiamento nella precisione dei dati e le analisi del flusso di controllo . (La trasformazione del CPS aiuta in qualche modo, fa male in altri, anche se i modi in cui aiuta sono principalmente dovuti alla duplicazione piuttosto che all'aspetto CPS stesso.) 1 Naturalmente, è possibile aggiungere regole e regolare le analisi per compensare ciò (vale a dire sfruttare gli invarianti globali), ma poi hai parzialmente sconfitto uno dei maggiori vantaggi della compilazione basata su CPS, ovvero che molte (apparentemente) ottimizzazioni ad hoc per scopi speciali diventano casi speciali di ottimizzazione per scopi generali (in particolare riduzione beta ).

In definitiva, a meno che la tua lingua non disponga di operatori di controllo, di solito non vi sono molte ragioni per utilizzare uno schema di compilazione basato su CPS. Una volta che hai compensato per i problemi che ho menzionato sopra, in genere hai eliminato i vantaggi della compilazione basata su CPS e prodotto qualcosa di equivalente al non utilizzo di CPS. A quel punto, CPS sta solo realizzando un codice intermedio dall'aspetto contorto per non molto beneficio. Un argomento per la compilazione basata su CPS del 2007 affronta alcuni di questi problemi e presenta altri vantaggi utilizzando una diversa forma di conversione CPS. Le cose che la carta riporta sono in parte coperte dal documento (2017) che ho menzionato prima.

1 L'equivalenza tra SSA e CPS non rende questo più o meno impossibile? No. Una delle prime cose che il documento che introduce questa equivalenza afferma è che l'equivalenza non funziona per il codice CPS arbitrario , ma funziona per l'output di una trasformazione CPS (che definiscono) per un linguaggio senza operatori di controllo.


2
È passato un po 'di tempo dalla risposta iniziale, ma mi sento abbastanza bene da accettare finalmente la risposta perché capisco più del 10% ...
CinchBlue,

2

Non sono un esperto di compilatore, quindi prendi quello che dico con un pizzico di sale, spero che un vero esperto di compilatore interverrà. Mi è stato detto che:

  • Il codice CPS tende a saltare molto non localmente, il che rovina la localizzazione della cache, quindi le prestazioni.
  • CPS richiede una garbage collection più costosa rispetto alla compilazione convenzionale, che in genere può archiviare molte cose nello stack (che è più veloce da allocare e deallocare).

Entrambi cospirano per rendere il codice compilato da CPS-Transform relativamente lento.


2
Penso che stai mescolando il codice sorgente trasformato CPS (o una trasformazione da fonte a fonte) con l'utilizzo di CPS in un linguaggio intermedio . Supponendo che la tua lingua di input non supporti effetti di controllo come call/cc, quindi, ad esempio, le continuazioni verranno utilizzate con una disciplina dello stack - non sarà necessario alcun garbage collector e le continuazioni corrisponderanno, più o meno, ai bordi del flusso di controllo grafico quindi non ci sarà alcun salto in più. Non è necessario implementare le continuazioni utilizzando alcune implementazioni di ordine generale di funzioni di ordine superiore.
Derek Elkins lasciò SE il

@DerekElkins Non mi era chiaro cosa chiedesse l'OP.
Martin Berger,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.