Posso aggiungere alcuni dettagli importanti sul sistema di ottimizzazione di GHC.
La definizione ingenua di foldr
passa attorno a una funzione. C'è un sovraccarico inerente nel chiamare una funzione, specialmente quando la funzione non è nota al momento della compilazione. Sarebbe davvero bello poter allineare la definizione della funzione se è nota al momento della compilazione.
Ci sono trucchi disponibili per eseguire tale inlining in GHC - e questo è un esempio di questi. Innanzitutto, è necessario inserire foldr
(verrà spiegato il motivo dopo). L'implementazione ingenua di foldr
è ricorsiva, quindi non può essere sottolineata. Quindi una trasformazione worker/wrapper viene applicata alla definizione. Il lavoratore è ricorsivo, ma il wrapper non lo è. Ciò consente di allineare foldr
, nonostante la ricorsione sulla struttura dell'elenco.
Quando foldr
è in linea, crea anche una copia di tutti i collegamenti locali. È più o meno un inlining testuale diretto (modulo alcuni rinominare, e succede dopo il passaggio di desugaring). Questo è dove le cose si fanno interessanti. go
è un'associazione locale e l'ottimizzatore deve cercare al suo interno. Si accorge che chiama una funzione nell'ambito locale, che chiama k
. GHC rimuoverà spesso la variabile k
interamente e la sostituirà semplicemente con l'espressione k
ridotta a. E poi in seguito, se l'applicazione della funzione è suscettibile di inlining, può essere inline in questo momento - rimuovendo il sovraccarico di chiamare completamente una funzione di prima classe.
Diamo un'occhiata a un esempio semplice e concreto. Questo programma eco di una linea di ingresso con tutti i finali 'x'
caratteri rimossi:
dropR :: Char -> String -> String
dropR x r = if x == 'x' && null r then "" else x : r
main :: IO()
main = do
s <- getLine
putStrLn $ foldr dropR "" s
Innanzitutto, l'ottimizzatore inline foldr
s' definizioni e semplificare, con conseguente codice che assomigli a questo:
main :: IO()
main = do
s <- getLine
-- I'm changing the where clause to a let expression for the sake of readability
putStrLn $ let { go [] = ""; go (x:xs) = dropR x (go xs) } in go s
E questa è la cosa che consente la trasformazione worker-wrapper .. Salterò i passaggi rimanenti, ma dovrebbe essere ovvio che GHC può ora incorporare la definizione di dropR
, eliminando l'overhead di chiamata della funzione. È qui che arriva la grande vittoria per le prestazioni.
Un dettaglio non menzionato ancora: ghc solo inline una funzione quando è completamente applicata, * sintatticamente *, nella sua parte sinistra. Questo è piuttosto strano e brutto se sei abituato a pensare di fare curriculum e creare un bel codice point-free-style. Questo è il motivo per cui a volte vedi lambda stupido alla destra di '=' nel codice ottimizzato. – jberryman