I linguaggi di programmazione funzionale hanno più opportunità di eseguire l'ottimizzazione del tempo di compilazione?


10

Stavo leggendo il libro "Programmazione funzionale per il mondo reale". È iniziato con il confronto tra linguaggi di programmazione imperativo e funzionale. E ha affermato come "valori" ed "espressioni" nella programmazione funzionale siano diversi dalle "variabili" e dalle "funzioni" della programmazione imperativa. Dalla discussione ho sviluppato un'idea che -

I linguaggi di programmazione funzionale hanno più opportunità di eseguire l'ottimizzazione del tempo di compilazione rispetto alle loro controparti imperative.

È vero?

Risposte:


16

I linguaggi di programmazione funzionale ottimizzano molto di più i tempi di compilazione. Uno dei motivi è la purezza: la concorrenza è banale perché non c'è stato. Quindi il compilatore può prendere due rami e concorcolarli facilmente senza cambiare il comportamento del programma.

Allo stesso tempo, tutto ciò che può essere calcolato senza stato (cioè qualsiasi cosa non monadica in haskell) può essere calcolato in anticipo dal compilatore, ma tali calcoli potrebbero essere costosi e quindi probabilmente vengono eseguiti solo parzialmente.

Inoltre, tutto ciò che non è necessario dal punto di vista computazionale può essere completamente ottimizzato dal programma.


1
+1: la programmazione gratuita con effetti collaterali è molto, molto facile da ottimizzare.
S.Lott

@mathepic: in realtà la parallelizzazione (in Haskell) avviene sia in fase di compilazione che in fase di esecuzione. Compile-time decide se vale la pena creare o meno un frammento (seed thread) e runtime elabora i frammenti come può, a seconda del numero di thread specificati. Se si specifica solo un singolo thread, i frammenti vengono creati, ma vengono elaborati uno dopo l'altro.
Matthieu M.,

2
@mathepic: un altro uso della purezza -> pigrizia e assenza di calcolo. Se puoi dimostrare che il valore non è necessario, quindi elimina completamente il calcolo. Se può essere necessario, utilizzare un thunk pigro. Se sai che sarà necessario, calcola subito (per evitare il lazy overhead). La purezza è (solo) incredibile :)
Matthieu M.

@Matthieu buon punto
alternativa

1
@Masse Anche la monade IO è pura. È solo che il risultato di "eseguire" la monade IO non lo è.
alternativa

7

Che ci siano in linea di principio più possibilità di ottimizzazione del tempo di compilazione per i linguaggi funzionali che per le loro controparti imperative è probabilmente vero.

Più interessante, tuttavia, è se sono effettivamente implementati negli attuali compilatori e quanto sono rilevanti in pratica queste ottimizzazioni (ovvero le prestazioni finali del codice idiomatico di "vita reale (TM)" in un ambiente di produzione, con impostazioni di compilatore a priori prevedibili).

ad esempio, i contributi di Haskell per il famigerato gioco di benchmark linguistici su computer (per quanto brutto possa essere - ma non è che al momento ci sia - al momento - qualcosa di significativamente migliore là fuori) dà l'impressione che sia stato speso un notevole periodo di tempo le ottimizzazioni manuali, che si sono confrontate con l'affermazione su "possibili ottimizzazioni del compilatore dovute a insert some property about FP languages here" fa sembrare che le ottimizzazioni siano (attualmente almeno) più una possibilità teorica che una realtà rilevante.

Sarei felice di essere smentito su questo punto.


1
Il motivo delle ottimizzazioni manuali in Haskell ha più a che fare con il fatto che alcune operazioni "semplici" richiedono tempo (dal punto di vista della complessità) in Haskell. Ad esempio, supponiamo di voler ottenere l'ultimo elemento di un elenco. In Java, questa è un'operazione piuttosto semplice; in Haskell, l'approccio ingenuo richiede di percorrere l'intera lista fino alla fine (a causa della natura pigra delle liste), rendendola un'operazione O (n). È qui (in parte) che arrivano le ottimizzazioni manuali.
mipadi,

2
Sono sicuro che ci sono validi motivi per l'ottimizzazione a mano di Haskell, ma essendo necessari per operazioni "semplici" dà l'impressione che le maggiori opportunità di ottimizzare il codice siano (attualmente) rilevanti solo in teoria.
Alexander Battisti,

6
È più la differenza tra l'ottimizzazione degli algoritmi e l'ottimizzazione del codice generato. Facciamo un esempio in C: se sto scrivendo un algoritmo di ricerca, posso scrivere un algoritmo ingenuo che passa semplicemente attraverso un elenco, oppure posso usare la ricerca binaria. In entrambi i casi, il compilatore ottimizzerà il mio codice, ma non trasformerà un algoritmo di ricerca ingenuo in una ricerca binaria. Molte ottimizzazioni manuali nel codice Haskell hanno più a che fare con l'ottimizzazione degli algoritmi stessi, piuttosto che con l'ottimizzazione del codice generato.
mipadi,

2
@mipadi, ma sembra che la versione semplice dell'algoritmo di Haskell non sia buona come la versione semplice degli algoritmi di altre lingue. (Sospetto a causa della mancata corrispondenza tra il modello funzionale e l'architettura del computer) Anche se è bravo a generare codice, non è abbastanza per superare questo problema.
Winston Ewert,

>> cattivo come potrebbe essere << - Sai se è cattivo o no? Che cosa vuoi dire anche suggerendo che è "cattivo"? Si prega di essere specifici.
igouy,

2

Nello stile funzionale, il flusso di valori attraverso un programma è molto, molto visibile (sia per il compilatore che per il programmatore). Ciò offre al compilatore un ampio margine di manovra per decidere dove archiviare i valori, per quanto tempo conservarli e così via.

In un linguaggio imperativo, il compilatore promette al programmatore un modello in cui la maggior parte delle variabili corrispondono a posizioni effettive in memoria che rimangono in circolazione per una vita definita. Potenzialmente, qualsiasi istruzione può leggere (o scrivere su!) Una di queste posizioni, quindi il compilatore può sostituire solo le posizioni di memoria con allocazione dei registri, unire due variabili in una singola posizione di archiviazione o eseguire ottimizzazioni simili dopo aver eseguito un'attenta analisi di dove altrimenti nel programma quella variabile può essere referenziata.

Ora, ci sono due avvertimenti:

  • La comunità dei linguaggi di programmazione ha speso (sprecato?) Molti sforzi negli ultimi cinquant'anni per sviluppare modi intelligenti di fare questa analisi. Ci sono trucchi ben noti come la colorazione dei registri e così via per ottenere alcuni dei casi più semplici fatti la maggior parte del tempo; ma questo rende i compilatori grandi e lenti e un costante compromesso della complessità della compilazione per la qualità del codice risultante
  • Allo stesso tempo, la maggior parte dei linguaggi di programmazione funzionale non sono puramente funzionali; molte cose che i programmi devono effettivamente fare, come rispondere agli I / O, sono intrinsecamente non funzionali, quindi nessun compilatore può essere completamente libero da questi trucchi, e nessun linguaggio li evita del tutto - anche Haskell, che è un po ' troppo puro per i miei gusti (il tuo chilometraggio può variare) può solo controllare e bloccare le parti non funzionali del codice, non evitarle del tutto.

Ma per rispondere alla domanda generale, sì, un paradigma funzionale offre al compilatore molta libertà di ottimizzare che non ha in un contesto imperativo.


Tutto in Haskell è funzionale, è solo che la tua mainè una funzione di trasformazione dello stato piuttosto che qualcosa che utilizza lo stato stesso.
alternativa il

Sì e no - concettualmente, è del tutto corretto affermare che un programma Haskell è una pura funzione delle interazioni dell'utente con quel programma (e lo stato del generatore di numeri casuali del sistema, e la latenza della rete oggi e qualsiasi altro intrinsecamente input non funzionali a cui il programma risponde), ma in pratica è una distinzione senza differenze.
jimwise,

@jimwise La trasparenza referenziale è una differenza molto grande.
alternativa il

Solo che non ce l'hai davvero, almeno per lo scopo discusso qui. Il punto di operazioni come IO, o generazione di numeri casuali, è che dovrebbero restituire un valore diverso ogni volta che vengono invocati con gli stessi input, e questo intrinsecamente limita il ragionamento su di essi funzionalmente, sia per il programmatore che per il compilatore.
jimwise,

1
@mathepic Sì, ovviamente puoi visualizzare concettualmente la casualità o l'input dell'utente (o la latenza di rete o il carico del sistema) come un flusso infinito o una funzione da stato a stato, ma non è una vista che si presta a ragionamenti utili sul comportamento del programma tu o il tuo compilatore - Bob Harper tratta bene questo argomento in un recente post sul suo blog sul nuovo curriculum CS di programmazione funzionale-CMU.
jimwise,
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.