Skip to content

Programação Paralela e Distribuída

Andre Chalom edited this page Nov 11, 2016 · 8 revisions

Introdução

O uso básico do cluster envolve rodar simulações escritas em C/C++ ou R e o uso de outras ferramentas de simulação e análise de dados.

Uma forma eficiente de gerenciar execuções em vários nós é usando o padrão de Message Passing Interface (MPI), implementado pelo MPICH2 e utilizado em R pelos pacotes Rmpi ou *paralell. Usar MPI tem as seguintes vantagens:

  • Padronização: MPI é um padrão vastamente utilizado em computação de alta performance.
  • Portabilidade: Códigos escritos para executar em MPI não precisam ser modificados para rodar em outras arquiteturas, ou se a arquitetura deste cluster foi alterada (os administradores precisam atualizar a configuração apenas uma vez).
  • Performance: Um programa que aproveite as vantagens do MPI vai executar mais rápido do que distribuir o código em vários nós “na mão”.
  • Agendamento: Embora o padrão MPI em si não permita o gerenciamento de recursos ENTRE jobs (apenas dentro de um mesmo processo), existem gerenciadores de recursos que podem ser usados para gerenciar o trabalho de vários usuários, disponibilizando recursos dos nós livres e monitorando a execução do trabalho. Não há nenhum agendador em execução no momento, mas em algum momento futuro eles serão instalados. SLURM e o Open Grid Scheduler são candidatos promissores.

Uso responsável de recursos

O cluster abacus, no momento, não possui cotas de uso de memória, cpu ou espaço em disco. No entanto, lembre-se sempre que ele é um recurso compartilhado por muitos usuários, portanto use sempre os recursos de forma responsável, e lembre-se de liberar os recursos de volta ao fim do trabalho. Isso envolve:

  • Encerrar sessões de MPI (usando mpiallexit)
  • Remover outputs de simulações do servidor
  • Verificar programas travados ou em loop

Ainda, lembre-se que um código eficiente ocupa menos recursos. Tente otimizar seu código antes de submetê-lo para execução no cluster. Se o seu código está em C/C++, um bom passo é fazer uma perfilhagem do código (profiling) para identificar quais funções demoram mais para executar. Um guia razoavelmente completo está em http://www.cs.utah.edu/dept/old/texinfo/as/gprof.html

O script cluster_stat mostra o estado de todos os nós do cluster, incluindo: (1) uso de CPU, (2) uso de memória e (3) usuários executando tarefas. Use esse script antes de iniciar suas simulações para identificar quais nós estão livres.

Programando em C/C++

Programas em C/C++/Fortran precisam ser escritos de forma a identificar o código que deve ser distribuído, e:

  • Fazer as chamadas para inicializar o trabalho distribuído, como MPI_Init()
  • Executar o código normal do programa, passando mensagens de um processo a outro se necessário
  • Finalizar o trabalho distribuído e coletar o resultado do código distribuído - MPI_Finalize()

Cada processo distribuído recebe um rank que o identifica. Um código que executa várias simulações independentes pode usar esse rank para “fatiar” o conjunto de entradas que deve usar (rodando 100 entradas em 2 nós, o nó 0 fica com as entradas 0-49 e o nó 1 com as 50-99. já com 4 nós, o nó 0 fica com as entradas 0-25, etc,etc). Já um programa que dependa do que está acontecendo em outros processos pode enviar e receber mensagens de status em diferentes formas.

Veja um exemplo rudimentar no arquivo /usr/local/share/examples_mpi/hello.c.

Usando MPI para simulações em C/C++

Se o código do seu programa em C/C++ está escrito usando chamadas do MPI, a tarefa de compilar e executar o programa é bem simples. Verifique quais nós estão disponíveis para uso (usando o script cluster_stat), e edite um arquivo contendo quais máquinas você quer usar. Por exemplo,

user@abacus:~$ cat mpd.hosts
abacus0000:3
abacus0001:4
abacus0002:2
#abacus0003:8

Os números depois dos dois pontos indicam quantos processos serão iniciados em cada máquina. Assim, o total de processos que pode ser iniciado é 9. Se um servidor não estiver listado nesse arquivo, ou estiver comentado (com # antes do nome) ele não vai receber nenhum processo. NUNCA inclua o master (abacus) nesta lista!

Além disso, você precisa criar um arquivo chamado .mpd.conf com uma palavra secreta. Por exemplo

echo "MPD_SECRETWORD=muitosecreto" >> ~/.mpd.conf
chmod 0600 ~/.mpd.conf

Substitua "muitosecreto" por algum outro segredo! Para compilar um programa no cluster, simplesmente use o “wrapper” mpicc ao invés do gcc:

user@abacus:~$ cp /usr/local/share/examples_mpi/hello.c hello.c
user@abacus:~$ mpicc hello.c -o hello

Para rodar um programa, primeiro inicie o mpd nas máquinas que você vai usar.

user@abacus:~$ mpdboot -n 4 -f mpd.hosts

O número usado na opção -n é o número de nós onde o mpd vai ser iniciado. Se você estiver rodando isso a partir de um nó que não vai ser usado pelo programa, inclua esse nó na conta. Neste exemplo, estamos ligando o mpd nos 3 nós (abacus0000 a abacus0002) que vamos usar, e no master; portanto, n vale 4.

Enfim, rode o programa usando mpirun:

user@abacus:~$ mpirun -machinefile mpd.hosts -np 9 ./hello
Process 0 on abacus0000 out of 9
Process 2 on abacus0000 out of 9
Process 1 on abacus0000 out of 9
Process 8 on abacus0002 out of 9
Process 5 on abacus0001 out of 9
Process 6 on abacus0001 out of 9
Process 4 on abacus0001 out of 9
Process 7 on abacus0002 out of 9
Process 3 on abacus0001 out of 9

O argumento -np aqui indica o número de processos que vai ser iniciado. Note que (1) o número de processos deve ser menor ou igual ao máximo disponível pelo arquivo mpd.hosts, (2) a configuração -machinefile deve aparecer na linha de comando antes de -np.

Por fim, depois de finalizar a simulação, não se esqueça de executar o comando para limpar os recursos compartilhados:

user@abacus:~$ mpdallexit

Programação paralela em R

A forma mais simples de usar programação paralela em R é usar a biblioteca "parallel" (que já vem instalada por padrão no R). Ela implementa uma série de protocolos para criar "clusters", aqui vamos nos focar no tipo "PSOCK".

Você pode criar um "cluster" especificando a lista de hosts que você quer usar; mas isso só vai ligar um processo em cada host. Para usar um arquivo no formato do "mpd.hosts" descrito acima, use a função "machinefile" do pacote "pse":

library(pse)
library(parallel)
cl = makePSOCKcluster(machinefile("mpd.hosts"))

A forma mais simples de executar código distribuído é usando as funções "clusterCall" ou "parSapply" (e suas "irmãs" como o parLapply):

ret1 <- clusterCall(cl,rnorm,100,0,1)
parSapply(cl,ret1,summary)

Para que um objeto esteja disponível em todos os nós, é necessário exporta-lo antes:

xx<- seq(0,100,.1)
clusterExport(cl,"xx")
ret2<-clusterCall(cl, function(y) xx + y, 10)
parSapply(cl,ret2,summary)

Finalmente, é importante encerrar o uso dos recursos antes de encerrar a sessão.

stopCluster(cl)

Veja mais informações na vinheta da biblioteca "parallel".

Clone this wiki locally