La programmazione funzionale non consente programmi più veloci, come regola generale. Ciò che rende è per più facile programmazione parallela e simultanea. Ci sono due chiavi principali per questo:
- L'evitamento dello stato mutabile tende a ridurre il numero di cose che possono andare storte in un programma, e ancora di più in un programma concorrente.
- L'evitamento della memoria condivisa e delle primitive di sincronizzazione basate su lock a favore di concetti di livello superiore tende a semplificare la sincronizzazione tra i thread del codice.
Un eccellente esempio del punto # 2 è che in Haskell abbiamo una chiara distinzione tra parallelismo deterministico vs concorrenza non deterministica . Non c'è spiegazione migliore della citazione dell'eccellente libro di Simon Marlow Programmazione parallela e simultanea in Haskell ( le citazioni provengono da Capitolo 1 ):
A parallel program is one that uses a multiplicity of computational hardware (e.g., several processor cores) to perform a computation more quickly. The aim is to arrive at the answer earlier, by delegating different parts of the computation to different processors that execute at the same time.
By contrast, concurrency is a program-structuring technique in which there are multiple threads of control. Conceptually, the threads of control execute “at the same time”; that is, the user sees their effects interleaved. Whether they actually execute at the same time or not is an implementation detail; a concurrent program can execute on a single processor through interleaved execution or on multiple physical processors.
In aggiunta a ciò, Marlow menziona anche la dimensione del determinismo :
A related distinction is between deterministic and nondeterministic programming models. A deterministic programming model is one in which each program can give only one result, whereas a nondeterministic programming model admits programs that may have different results, depending on some aspect of the execution. Concurrent programming models are necessarily nondeterministic because they must interact with external agents that cause events at unpredictable times. Nondeterminism has some notable drawbacks, however: Programs become significantly harder to test and reason about.
For parallel programming, we would like to use deterministic programming models if at all possible. Since the goal is just to arrive at the answer more quickly, we would rather not make our program harder to debug in the process. Deterministic parallel programming is the best of both worlds: Testing, debugging, and reasoning can be performed on the sequential program, but the program runs faster with the addition of more processors.
In Haskell il parallelismo e le caratteristiche di concorrenza sono disegnate attorno a questi concetti. In particolare, quali altre lingue sono raggruppate come un set di funzionalità, Haskell si divide in due:
-
Funzioni deterministiche e librerie per parallelismo .
-
Funzioni e librerie non deterministiche per concorrenza .
Se stai solo cercando di accelerare un calcolo puro e deterministico, avere un parallelismo deterministico spesso rende le cose molto più semplici. Spesso fai qualcosa 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 finché un consumatore non li richiede.
- Utilizza la libreria Strategie per utilizzare gli elementi delle liste dei risultati della tua funzione in parallelo su più core .
In realtà l'ho fatto con uno dei miei programmi di progetti giocattolo alcune settimane fa . È stato banale parallelizzare il programma: la cosa fondamentale che dovevo fare era, in effetti, aggiungere del codice che diceva "calcola gli elementi di questo elenco in parallelo" (riga 90), e ho ottenuto un aumento del throughput quasi lineare in alcuni dei miei casi di test più costosi.
Il mio programma è più veloce di se fossi andato con le utility di multithreading basate su lock convenzionali? Ne dubito molto. La cosa bella nel mio caso è stata ottenere così tanto da un così piccolo dollaro - il mio codice è probabilmente molto subottimale, ma poiché è così facile da parallelizzare ho avuto una grande accelerazione con molto meno sforzo di una corretta profilazione e 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".