La programmazione funzionale non consente programmi più veloci, come regola generale. Ciò che rende è una programmazione parallela e simultanea più semplice . Ci sono due chiavi principali per questo:
- Evitare lo stato mutevole tende a ridurre il numero di cose che possono andare storte in un programma, e ancora di più in un programma concorrente.
- Evitare primitive di sincronizzazione basate su memoria condivisa e lock a favore di concetti di livello superiore tende a semplificare la sincronizzazione tra thread di codice.
Un ottimo esempio di punto # 2 è che in Haskell abbiamo una chiara distinzione tra il parallelismo deterministico contro la concorrenza non deterministica . Non c'è spiegazione migliore di citare l'eccellente libro di Simon Marlow Parallel and Concurrent Programming in Haskell (le citazioni sono dal capitolo 1 ):
Un programma parallelo è uno che utilizza una molteplicità di hardware computazionale (ad esempio, diversi core del processore) per eseguire un calcolo più rapidamente. L'obiettivo è quello di arrivare alla risposta prima, delegando parti diverse del calcolo a processori diversi che eseguono contemporaneamente.
Al contrario, la concorrenza è una tecnica di strutturazione del programma in cui esistono più thread di controllo. Concettualmente, i thread di controllo vengono eseguiti "allo stesso tempo"; cioè, l'utente vede i loro effetti interlacciati. Se eseguono effettivamente contemporaneamente o meno è un dettaglio di implementazione; un programma simultaneo può essere eseguito su un singolo processore attraverso l'esecuzione interfogliata o su più processori fisici.
Oltre a ciò, Marlow menziona anche la dimensione del determinismo :
Una distinzione correlata è tra i modelli di programmazione deterministica e non deterministica . Un modello di programmazione deterministica è quello in cui ciascun programma può dare un solo risultato, mentre un modello di programmazione non deterministico ammette programmi che possono avere risultati diversi, a seconda di alcuni aspetti dell'esecuzione. I modelli di programmazione simultanea sono necessariamente non deterministici perché devono interagire con agenti esterni che causano eventi in tempi imprevedibili. Il non determinismo presenta alcuni notevoli svantaggi, tuttavia: i programmi diventano significativamente più difficili da testare e ragionare.
Per la programmazione parallela, vorremmo utilizzare modelli di programmazione deterministica, se possibile. Poiché l'obiettivo è solo quello di arrivare alla risposta più rapidamente, preferiremmo non rendere più difficile il debug del nostro programma nel processo. La programmazione parallela deterministica è il migliore dei due mondi: test, debug e ragionamento possono essere eseguiti sul programma sequenziale, ma il programma funziona più velocemente con l'aggiunta di più processori.
In Haskell le caratteristiche di parallelismo e concorrenza sono progettate attorno a questi concetti. In particolare, quali altre lingue raggruppano insieme come un set di funzionalità, Haskell si divide in due:
- Funzionalità e librerie deterministiche per il parallelismo .
- Funzionalità e librerie non deterministiche per la concorrenza .
Se stai solo cercando di accelerare un calcolo puro e deterministico, avere un parallelismo deterministico spesso rende le cose molto più facili. Spesso fai semplicemente una cosa del genere:
- Scrivi una funzione che produce un elenco di risposte, ognuna delle quali è costosa da calcolare ma non dipende molto l'una dall'altra. Questo è Haskell, quindi gli elenchi sono pigri: i valori dei loro elementi non vengono effettivamente calcolati fino a quando un consumatore non li richiede.
- Utilizzare la libreria Strategie per utilizzare gli elementi dell'elenco dei risultati della funzione in parallelo su più core.
In realtà l'ho fatto con uno dei miei programmi di progetti di giocattoli poche settimane fa . Parallelizzare il programma era banale: la cosa chiave che dovevo fare era, in effetti, aggiungere un po 'di codice che dicesse "calcola gli elementi di questo elenco in parallelo" (linea 90), e ho ottenuto un incremento della velocità quasi lineare in alcuni dei miei casi di test più costosi.
Il mio programma è più veloce di se avessi utilizzato le utility di multithreading basate su lock convenzionali? Ne dubito molto. La cosa bella nel mio caso è stata quella di ottenere così tanto botto da così poco dollaro: il mio codice è probabilmente molto subottimale, ma poiché è così facile da parallelizzare ho ottenuto una grande accelerazione con molto meno sforzo rispetto alla profilazione e all'ottimizzazione, e nessun rischio di condizioni di gara. E questo, direi, è il modo principale in cui la programmazione funzionale consente di scrivere programmi "più veloci".