Estou mergulhando no mundo da programação funcional e continuo lendo em todos os lugares que linguagens funcionais são melhores para programas multithreading / multicore. Eu entendo como as linguagens funcionais fazem muitas coisas de maneira diferente, como recursão , números aleatórios etc., mas não consigo descobrir se o multithreading é mais rápido em uma linguagem funcional porque é compilado de maneira diferente ou porque eu o escrevo de maneira diferente.
Por exemplo, eu escrevi um programa em Java que implementa um determinado protocolo. Nesse protocolo, as duas partes enviam e recebem umas às outras milhares de mensagens, criptografam essas mensagens e as reenviam (e as recebem) repetidamente. Como esperado, o multithreading é essencial quando você lida na escala de milhares. Neste programa não há bloqueio envolvido .
Se eu escrever o mesmo programa no Scala (que usa a JVM), essa implementação será mais rápida? Se sim, por que? É por causa do estilo de escrita? Se é por causa do estilo de escrita, agora que o Java inclui expressões lambda, não foi possível obter os mesmos resultados usando Java com lambda? Ou é mais rápido porque o Scala compilará as coisas de maneira diferente?
fonte
Respostas:
A razão pela qual as pessoas dizem que as linguagens funcionais são melhores para o processamento paralelo se deve ao fato de geralmente evitarem um estado mutável. Estado mutável é a "raiz de todo mal" no contexto do processamento paralelo; eles facilitam muito as condições de corrida quando são compartilhados entre processos simultâneos. A solução para as condições de corrida envolve mecanismos de bloqueio e sincronização, como você mencionou, que causam sobrecarga no tempo de execução, pois os processos esperam um pelo outro para fazer uso do recurso compartilhado e maior complexidade de design, pois todos esses conceitos tendem a ser profundamente aninhado em tais aplicativos.
Quando você evita um estado mutável, a necessidade de mecanismos de sincronização e bloqueio desaparece junto com ele. Como as linguagens funcionais geralmente evitam o estado mutável, elas são naturalmente mais eficientes e eficazes para o processamento paralelo - você não terá a sobrecarga de tempo de execução dos recursos compartilhados e a complexidade de design adicional que geralmente se segue.
No entanto, tudo isso é incidental. Se sua solução em Java também evitar um estado mutável (compartilhado especificamente entre threads), a conversão para uma linguagem funcional como Scala ou Clojure não trará nenhum benefício em termos de eficiência simultânea, porque a solução original já está livre da sobrecarga causada por os mecanismos de bloqueio e sincronização.
TL; DR: se uma solução no Scala é mais eficiente no processamento paralelo do que uma em Java, não é por causa da maneira como o código é compilado ou executado pela JVM, mas porque a solução Java está compartilhando um estado mutável entre os encadeamentos, causando condições de corrida ou adicionando a sobrecarga de sincronização para evitá-las.
fonte
Tipo de ambos. É mais rápido, porque é mais fácil escrever seu código de uma maneira mais fácil de compilar mais rapidamente. Você não necessariamente terá uma diferença de velocidade alternando idiomas, mas se tivesse iniciado com uma linguagem funcional, provavelmente poderia ter feito o multithreading com muito menos esforço do programador . Na mesma linha, é muito mais fácil para um programador cometer erros de segmentação que custarão velocidade em uma linguagem imperativa e muito mais difícil perceber esses erros.
O motivo é que os programadores imperativos geralmente tentam colocar todo o código encadeado sem bloqueio na menor caixa possível e escapar o mais rápido possível, de volta ao seu confortável mundo síncrono e mutável. A maioria dos erros que lhe custam velocidade são cometidos nessa interface de limite. Em uma linguagem de programação funcional, você não precisa se preocupar tanto em cometer erros nesse limite. A maior parte do seu código de chamada também está "dentro da caixa", por assim dizer.
fonte
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:
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 ):
Além disso, Marlow menciona também traz a dimensão do determinismo :
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:
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:
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".
fonte
Em Haskell, a modificação é literalmente impossível sem obter variáveis modificáveis especiais através de uma biblioteca de modificações. Em vez disso, as funções criam as variáveis necessárias ao mesmo tempo que seus valores (que são computados preguiçosamente) e o lixo coletado quando não é mais necessário.
Mesmo quando você precisa de variáveis de modificação, geralmente é possível usá-lo com moderação e com variáveis não modificáveis. (Outra coisa interessante no haskell é o STM, que substitui bloqueios por operações atômicas, mas não tenho certeza se isso é apenas para programação funcional ou não.) Geralmente, apenas uma parte do programa precisará ser paralela para melhorar as coisas. em termos de desempenho.
Isso facilita o paralelismo em Haskell a maior parte do tempo e, de fato, estão sendo feitos esforços para torná-lo automático. Para código simples, o paralelismo e a lógica podem até ser separados.
Além disso, devido ao fato de que a ordem de avaliação não importa em Haskell, o compilador apenas cria uma fila de itens que precisam ser avaliados e os envia para quaisquer núcleos disponíveis, para que você possa criar um monte de "threads" que não importam na verdade, se tornam tópicos até necessário. A ordem de avaliação que não importa é característica da pureza, que geralmente requer programação funcional.
Leitura adicional:
Paralelismo em Haskell (HaskellWiki)
Programação simultânea e multicore em "Mundo Real Haskell"
Programação paralela e simultânea em Haskell por Simon Marlow
fonte
grep java this_post
.grep scala this_post
egrep jvm this_post
não retorne resultados :)