A programação funcional não cria programas mais rápidos, como regra geral. O que ele faz é facilitar a programação paralela e simultânea. Existem duas chaves principais para isso:
- Evitar o estado mutável tende a reduzir o número de coisas que podem dar errado em um programa, e mais ainda em um programa concorrente.
- Evitar a memória compartilhada e as primitivas de sincronização baseada em bloqueio em favor de conceitos de nível superior tende a simplificar a sincronização entre encadeamentos de código.
Um excelente exemplo do ponto 2 é que, em Haskell, temos uma clara distinção entre paralelismo determinístico versus concorrência não determinística . Não há explicação melhor do que citar o excelente livro de Simon Marlow, Parallel and Concurrent Programming in Haskell (as citações são do capítulo 1 ):
Um programa paralelo é aquele que usa uma multiplicidade de hardware computacional (por exemplo, vários núcleos de processador) para executar um cálculo mais rapidamente. O objetivo é chegar à resposta mais cedo, delegando diferentes partes da computação a diferentes processadores que são executados ao mesmo tempo.
Por outro lado, a simultaneidade é uma técnica de estruturação de programas na qual existem vários threads de controle. Conceitualmente, os threads de controle são executados “ao mesmo tempo”; isto é, o usuário vê seus efeitos intercalados. Se eles realmente são executados ao mesmo tempo ou não, é um detalhe da implementação; um programa simultâneo pode ser executado em um único processador através da execução intercalada ou em vários processadores físicos.
Além disso, Marlow menciona também traz a dimensão do determinismo :
Uma distinção relacionada é entre modelos de programação determinísticos e não determinísticos . Um modelo de programação determinístico é aquele em que cada programa pode fornecer apenas um resultado, enquanto um modelo de programação não determinístico admite programas que podem ter resultados diferentes, dependendo de algum aspecto da execução. Modelos de programação simultâneos são necessariamente não determinísticos, pois precisam interagir com agentes externos que causam eventos em momentos imprevisíveis. O não determinismo tem algumas desvantagens notáveis: os programas se tornam significativamente mais difíceis de testar e raciocinar.
Para programação paralela, gostaríamos de usar modelos de programação determinísticos, se possível. Como o objetivo é apenas chegar à resposta mais rapidamente, preferimos não tornar nosso programa mais difícil de depurar no processo. A programação paralela determinística é o melhor dos dois mundos: teste, depuração e raciocínio podem ser executados no programa seqüencial, mas o programa é executado mais rapidamente com a adição de mais processadores.
Em Haskell, os recursos de paralelismo e simultaneidade são projetados em torno desses conceitos. Em particular, que outros idiomas agrupam como um conjunto de recursos, Haskell se divide em dois:
- Recursos determinísticos e bibliotecas para paralelismo .
- Recursos não determinísticos e bibliotecas para simultaneidade .
Se você está apenas tentando acelerar uma computação determinística pura, ter paralelismo determinístico geralmente torna as coisas muito mais fáceis. Muitas vezes, você apenas faz algo assim:
- Escreva uma função que produza uma lista de respostas, cada uma das quais é cara de calcular, mas não depende muito uma da outra. Isso é Haskell, então as listas são preguiçosas - os valores de seus elementos não são computados até que um consumidor os exija.
- Use a biblioteca de estratégias para consumir os elementos das listas de resultados da sua função em paralelo em vários núcleos.
Na verdade, eu fiz isso com um dos meus programas de projetos de brinquedos há algumas semanas . Foi trivial paralelizar o programa - a principal coisa que tive que fazer foi, de fato, adicionar um código que diz "computar os elementos desta lista em paralelo" (linha 90), e obtive um aumento quase linear da taxa de transferência em alguns dos meus casos de teste mais caros.
Meu programa é mais rápido do que se eu tivesse usado utilitários multithreading convencionais baseados em bloqueio? Duvido muito. A coisa bacana no meu caso foi ganhar tanto dinheiro com tão pouco dinheiro - meu código provavelmente é muito abaixo do ideal, mas porque é tão fácil de paralelizar, consegui uma grande velocidade com muito menos esforço do que criar um perfil e otimizá-lo adequadamente, e sem risco de condições de corrida. E isso, eu diria, é a principal maneira pela qual a programação funcional permite que você escreva programas "mais rápidos".