Programowanie funkcjonalne z reguły nie powoduje szybszych programów. Pozwala to na łatwiejsze programowanie równoległe i współbieżne. Są do tego dwa główne klucze:
- Unikanie stanu zmiennego ma tendencję do zmniejszania liczby rzeczy, które mogą się nie udać w programie, a tym bardziej w programie współbieżnym.
- Unikanie operacji podstawowych synchronizacji z pamięcią współużytkowaną i blokadą na korzyść koncepcji wyższego poziomu zwykle upraszcza synchronizację między wątkami kodu.
Doskonałym przykładem punktu 2 jest to, że w Haskell mamy wyraźne rozróżnienie między równoległością deterministyczną a niedeterministyczną współbieżnością . Nie ma lepszego wytłumaczenia niż cytowanie doskonałej książki Simona Marlowa „ Programowanie równoległe i współbieżne” w Haskell (cytaty pochodzą z rozdziału 1 ):
Program równoległy to taki, który wykorzystuje wiele sprzętu obliczeniowego (np. Kilka rdzeni procesora) do szybszego wykonywania obliczeń. Celem jest wcześniejsze uzyskanie odpowiedzi poprzez przekazanie różnych części obliczeń do różnych procesorów wykonujących się w tym samym czasie.
Natomiast współbieżność jest techniką konstruowania programu, w której istnieje wiele wątków kontroli. Koncepcyjnie wątki kontroli wykonują się „w tym samym czasie”; to znaczy, użytkownik widzi, że ich efekty są przeplatane. To, czy faktycznie wykonują się w tym samym czasie, czy nie, jest szczegółem implementacji; współbieżny program może być wykonywany na jednym procesorze poprzez przeplatanie lub na wielu procesorach fizycznych.
Oprócz tego Marlow wspomina także o wymiarze determinizmu :
Powiązane jest rozróżnienie między deterministycznymi i niedeterministycznymi modelami programowania. Deterministyczny model programowania to taki, w którym każdy program może dawać tylko jeden wynik, podczas gdy niedeterministyczny model programowania dopuszcza programy, które mogą mieć różne wyniki, w zależności od niektórych aspektów wykonania. Współbieżne modele programowania są z konieczności niedeterministyczne, ponieważ muszą wchodzić w interakcje z agentami zewnętrznymi, które powodują zdarzenia w nieprzewidzianych momentach. Niedeterminizm ma jednak pewne istotne wady: Programy stają się znacznie trudniejsze do przetestowania i uzasadnienia.
W przypadku programowania równoległego chcielibyśmy stosować deterministyczne modele programowania, jeśli to w ogóle możliwe. Ponieważ celem jest po prostu szybsze uzyskanie odpowiedzi, wolelibyśmy, aby nasz program nie był trudniejszy do debugowania w tym procesie. Deterministyczne programowanie równoległe jest najlepsze z obu światów: testowanie, debugowanie i wnioskowanie można wykonywać na programie sekwencyjnym, ale program działa szybciej z dodaniem większej liczby procesorów.
W Haskell funkcje równoległości i współbieżności zostały zaprojektowane wokół tych koncepcji. W szczególności, jakie inne języki grupują razem jako jeden zestaw funkcji, Haskell dzieli się na dwa:
- Deterministyczne cechy i biblioteki dla równoległości .
- Niedeterministyczne cechy i biblioteki współbieżności .
Jeśli próbujesz tylko przyspieszyć czyste, deterministyczne obliczenia, posiadanie deterministycznego paralelizmu często znacznie ułatwia. Często po prostu robisz coś takiego:
- Napisz funkcję, która tworzy listę odpowiedzi, z których każda jest kosztowna do obliczenia, ale nie bardzo od siebie zależy. To jest Haskell, więc listy są leniwe - wartości ich elementów nie są obliczane, dopóki konsument ich nie zażąda.
- Użyj biblioteki Strategie, aby równolegle korzystać z elementów list wyników funkcji na wielu rdzeniach.
Zrobiłem to w jednym z moich programów zabawkowych kilka tygodni temu . Równoważenie programu było trywialne - kluczową rzeczą, którą musiałem zrobić, było dodanie kodu, który mówi „oblicz równolegle elementy tej listy” (linia 90), i uzyskałem prawie liniową poprawę przepustowości w niektóre z moich droższych przypadków testowych.
Czy mój program jest szybszy niż gdybym korzystał z konwencjonalnych narzędzi do wielowątkowości opartych na blokadach? Bardzo w to wątpię. W moim przypadku fajną rzeczą było uzyskanie tak wielkiego huku z tak małej złotówki - mój kod jest prawdopodobnie bardzo nieoptymalny, ale ponieważ jest tak łatwy do zrównoleglenia, uzyskałem duże przyspieszenie przy znacznie mniejszym wysiłku niż odpowiednie profilowanie i optymalizacja, i bez ryzyka warunków wyścigu. I to, jak twierdzę, jest to główny sposób, w jaki programowanie funkcjonalne pozwala pisać „szybsze” programy.