Acelere seu Python: 7 Frameworks poderosos para Computação Paralela

Acelere seu Python: 7 Frameworks poderosos para Computação Paralela

Python é uma linguagem de programação poderosa, versátil e amigável ao programador, mas não é a linguagem de programação mais rápida que existe. Algumas das limitações de velocidade do Python são devido à sua implementação padrão, CPython, sendo single-threaded. Ou seja, CPython não usa mais de um thread de hardware por vez.

E embora você possa usar o módulo interno do Python threading para acelerar as coisas, threading ele só lhe dá simultaneidade, não paralelismo. É bom para executar várias tarefas que não são dependentes da CPU, mas não faz nada para acelerar várias tarefas que exigem uma CPU cheia. O Python 3.13 introduziu uma construção especial de 'threading livre' ou 'sem GIL' do interpretador para permitir paralelismo total com threads do Python, mas ainda é considerado um recurso experimental. Por enquanto, é melhor assumir que o threading no Python, por padrão, não lhe dará paralelismo.

O Python inclui outra maneira nativa de executar uma carga de trabalho em várias CPUs. O multiprocessing módulo cria várias cópias do interpretador Python, cada uma em um núcleo separado, e fornece primitivas para dividir tarefas entre núcleos. Mas às vezes nem o multiprocessing módulo é suficiente.

Em alguns casos, o trabalho exige a distribuição do trabalho não apenas entre vários núcleos, mas também entre várias máquinas. É aí que entram as bibliotecas e frameworks Python discutidos neste artigo. Veremos sete frameworks que você pode usar para espalhar um aplicativo Python existente e sua carga de trabalho entre vários núcleos, várias máquinas ou ambos.

As melhores bibliotecas de processamento paralelo para Python

Ray

Desenvolvido por uma equipe de pesquisadores da Universidade da Califórnia, Berkeley, o Ray sustenta uma variedade de bibliotecas de machine learning distribuídas. Mas o Ray não se limita apenas a tarefas de machine learning, mesmo que esse fosse seu uso original pretendido. Você pode dividir e distribuir qualquer tipo de tarefa Python em vários sistemas com o Ray.

A sintaxe do Ray é mínima, então você não precisa retrabalhar aplicativos existentes extensivamente para paralelizá-los. O @ray.remote decorador distribui essa função em todos os nós disponíveis em um cluster Ray, com a opção de especificar parâmetros para quantas CPUs ou GPUs usar. Os resultados de cada função distribuída são retornados como objetos Python, então eles são fáceis de gerenciar e armazenar, e a quantidade de cópias entre nós ou dentro deles é mínima. Esse último recurso é útil ao lidar com matrizes NumPy, por exemplo.

O Ray ainda inclui um gerenciador de cluster integrado, que pode automaticamente girar nós conforme necessário em hardware local ou plataformas populares de computação em nuvem. Outras bibliotecas Ray permitem que você dimensione cargas de trabalho comuns de aprendizado de máquina e ciência de dados, para que você não precise fazer o scaffolding manualmente. Por exemplo, o Ray Tune permite que você execute o giro de hiperparâmetros em escala para a maioria dos sistemas comuns de aprendizado de máquina (PyTorch e TensorFlow, entre outros). E se tudo o que você quer fazer é dimensionar seu uso do módulo multiprocessing do Python, o Ray também pode fazer isso.

Dask

De fora, o Dask se parece muito com o Ray. Ele também é uma biblioteca para computação paralela distribuída em Python, com um sistema de agendamento de tarefas integrado, conhecimento de frameworks de dados Python como o NumPy e a capacidade de escalar de uma máquina para muitas.

Uma diferença fundamental entre Dask e Ray é o mecanismo de agendamento. O Dask usa um agendador centralizado que lida com todas as tarefas de um cluster. O Ray é descentralizado, o que significa que cada máquina executa seu próprio agendador, então quaisquer problemas com uma tarefa agendada são tratados no nível da máquina individual, não do cluster inteiro. A estrutura de tarefas do Dask trabalha lado a lado com concurrent.futures as interfaces nativas do Python, então para aqueles que usaram essa biblioteca, a maioria das metáforas de como os trabalhos funcionam devem ser familiares.

O Dask funciona de duas maneiras básicas. A primeira é usando estruturas de dados paralelizadas — essencialmente, as próprias versões do Dask de matrizes NumPy, listas ou Pandas DataFrames. Troque as versões do Dask dessas construções por seus padrões, e o Dask espalhará automaticamente sua execução pelo seu cluster. Isso normalmente envolve pouco mais do que alterar o nome de uma importação, mas às vezes pode exigir uma reescrita para funcionar completamente.

A segunda maneira é por meio dos mecanismos de paralelização de baixo nível do Dask, incluindo decoradores de função que distribuem tarefas entre nós e retornam os resultados de forma síncrona (no modo "imediato") ou assíncrona (modo "preguiçoso"). Você também pode misturar os modos conforme necessário.

Uma conveniência Pythonic que o Dask oferece é uma estrutura de memória chamada bag — essencialmente uma lista Python distribuída. Bags fornecem operações distribuídas (como map e filter) em coleções de objetos Python, com quaisquer otimizações que possam ser fornecidas para elas. A desvantagem é que qualquer operação que exija muita comunicação cruzada entre nós (por exemplo, groupby) não funcionará tão bem.

O Dask também oferece um recurso chamado actors. Um actor é um objeto que aponta para um trabalho em outro nó do Dask. Dessa forma, um trabalho que requer muito estado local pode ser executado no local e ser chamado remotamente por outros nós, então o estado do trabalho não precisa ser replicado. O modelo de actor do Dask suporta uma distribuição de trabalho mais sofisticada do que o Ray pode gerenciar. No entanto, o planejador do Dask não está ciente do que os atores fazem, então se um ator ficar descontrolado ou travar, o planejador não pode intervir. "Alto desempenho, mas não resiliente" é como a documentação coloca, então os atores devem ser usados com cuidado.

Dispy

O Dispy permite que você distribua programas Python inteiros ou apenas funções individuais em um cluster de máquinas para execução paralela. Ele usa mecanismos nativos de plataforma para comunicação de rede para manter as coisas rápidas e eficientes, então máquinas Linux, macOS e Windows funcionam igualmente bem. Isso o torna uma solução mais genérica do que outras discutidas aqui, então vale a pena dar uma olhada se você precisa de algo que não seja especificamente sobre acelerar tarefas de aprendizado de máquina ou uma estrutura de processamento de dados específica.

A sintaxe do Dispy lembra um pouco o módulo do Python multiprocessing, pois você cria explicitamente um cluster (onde multiprocessing você criaria um pool de processos), envia o trabalho para o cluster e, em seguida, recupera os resultados. Modificar os trabalhos para trabalhar com o Dispy pode exigir um pouco mais de trabalho, mas você ganha controle preciso sobre como esses trabalhos são despachados e retornados. Por exemplo, você pode retornar resultados provisórios ou parcialmente concluídos, transferir arquivos como parte do processo de distribuição de trabalhos e usar criptografia SSL ao transferir dados.

Pandaral·lel

Pandaral·lel, como o nome indica, é uma maneira de paralelizar trabalhos do Pandas em várias máquinas. A desvantagem é que o Pandaral·lel funciona apenas com o Pandas. Mas se o Pandas é o que você está usando, e tudo o que você precisa é de uma maneira de acelerar os trabalhos do Pandas em vários núcleos em um único computador, o Pandaral·lel é focado a laser na tarefa.

Observe que, embora o Pandaral·lel rode no Windows, ele rodará apenas em sessões Python iniciadas no Subsistema Windows para Linux. Usuários Linux e macOS podem executar o Pandaral·lel como está. Observe também que o Pandaral·lel atualmente não tem um mantenedor; seu último lançamento formal foi em maio de 2023.

Ipyparallel

Ipyparallel é outro sistema de multiprocessamento e distribuição de tarefas fortemente focado, especificamente para paralelizar a execução do código do Jupyter Notebook em um cluster. Projetos e equipes que já trabalham no Jupyter podem começar a usar o Ipyparallel imediatamente.

O Ipyparallel suporta muitas abordagens para paralelizar código. Na extremidade simples, há map, que aplica qualquer função a uma sequência e divide o trabalho uniformemente entre os nós disponíveis. Para trabalhos mais complexos, você pode decorar funções específicas para sempre executar remotamente ou em paralelo.

Os notebooks Jupyter suportam "comandos mágicos" para ações que só são possíveis em um ambiente de notebook. O Ipyparallel adiciona alguns comandos mágicos próprios. Por exemplo, você pode prefixar qualquer declaração Python com %px para paralelizá-la automaticamente.

Joblib

Joblib tem dois objetivos principais: executar jobs em paralelo e não recalcular resultados se nada mudou. Essas eficiências tornam Joblib bem adequado para computação científica, onde resultados reproduzíveis são sacrossantos. A documentação do Joblib fornece muitos exemplos de como usar todos os seus recursos.

A sintaxe Joblib para paralelizar trabalho é bastante simples — equivale a um decorador que pode ser usado para dividir trabalhos entre processadores ou para armazenar resultados em cache. Trabalhos paralelos podem usar threads ou processos.

Joblib inclui um cache de disco transparente para objetos Python criados por trabalhos de computação. Esse cache não só ajuda Joblib a evitar a repetição de trabalho, como observado acima, mas também pode ser usado para suspender e retomar trabalhos de longa execução ou continuar de onde um trabalho parou após uma falha. O cache também é otimizado de forma inteligente para objetos grandes, como matrizes NumPy. Regiões de dados podem ser compartilhadas na memória entre processos no mesmo sistema usando numpy.memmap. Tudo isso torna Joblib altamente útil para trabalhos que podem levar muito tempo para serem concluídos, pois você pode evitar refazer o trabalho existente e pausar e retomar conforme necessário.

Uma coisa que o Joblib não oferece é uma maneira de distribuir jobs entre vários computadores separados. Em teoria, é possível usar o pipeline do Joblib para fazer isso, mas provavelmente é mais fácil usar outro framework que suporte isso nativamente.

Parsl

Abreviação de "Parallel Scripting Library", Parsl permite que você pegue trabalhos de computação e os divida em vários sistemas usando aproximadamente a mesma sintaxe dos Pool objetos existentes do Python. Ele também permite que você junte diferentes tarefas de computação em fluxos de trabalho de várias etapas, que podem ser executados em paralelo, em sequência ou por meio de operações map/reduce.

O Parsl permite que você execute aplicativos Python nativos, mas também execute qualquer outro aplicativo externo por meio de comandos para o shell. Seu código Python é escrito como código Python normal, exceto por um decorador de função especial que marca o ponto de entrada para seu trabalho. O sistema de envio de tarefas também fornece controle refinado sobre como as coisas são executadas nos alvos — por exemplo, o número de núcleos por trabalhador, quanta memória por trabalhador, controles de afinidade de CPU, com que frequência pesquisar por timeouts e assim por diante.

Um excelente recurso que o Parsl oferece é um conjunto de modelos pré-construídos para despachar trabalho para uma variedade de recursos de computação de ponta. Isso não inclui apenas grampos como clusters AWS ou Kubernetes, mas recursos de supercomputação (assumindo que você tenha acesso) como Blue Waters, ASPIRE 1, Frontera e assim por diante. (O Parsl foi co-desenvolvido com a ajuda de muitas das instituições que construíram esse hardware.)

Conclusões

Se você estiver trabalhando com bibliotecas de aprendizado de máquina populares existentes que deseja executar de forma distribuída, Ray é sua escolha de primeira rodada. Para o mesmo tipo de trabalho, mas com agendamento centralizado (por exemplo, para ter controle de cima para baixo sobre os trabalhos enviados), use Dask. Se você quiser trabalhar especificamente com Pandas, use Pandaral·lel; para distribuição paralela de trabalho em notebooks Jupyter, use Ipyparallel. E, para cargas de trabalho Python mais genéricas, Dispy, Joblib e Parsl podem fornecer distribuição paralela de tarefas sem muitos extras.

As limitações do Python com threads continuarão a evoluir, com grandes mudanças programadas para permitir que threads rodem lado a lado para trabalho limitado pela CPU. Mas essas atualizações estão a anos de distância de serem utilizáveis. Bibliotecas projetadas para paralelismo podem ajudar a preencher a lacuna enquanto esperamos.

Conteúdo Relacionado

Torna al blog

Lascia un commento

Si prega di notare che, prima di essere pubblicati, i commenti devono essere approvati.