-
Notifications
You must be signed in to change notification settings - Fork 1
Programação Paralela e Distribuída
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 MPICH 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 podem ser instalados. SLURM e o Open Grid Scheduler são candidatos promissores.
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 R ou outros programas
- 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.
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 hello_mpi.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. Simplesmente use o “wrapper” mpicc ao invés do gcc. Para baixar e compilar nosso programa de exemplo:
user@abacus0001:~$ wget https://github.com/lageIBUSP/abacus/raw/master/testes/hello_mpi.c
user@abacus0001:~$ mpicc hello_mpi.c -o hello
Depois, 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@abacus0001:~$ cat mpi.hosts
abacus0000:3
abacus0001:4
abacus0002:2
#abacus0003:8
Os números depois dos dois pontos indicam quantos processos podem ser iniciados em cada máquina. Se você solicitar mais do que 9 processos, essa lista é "reciclada". 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!
Enfim, rode o programa usando mpirun:
user@abacus0000:~$ mpirun --hostfile mpi.hosts -n 9 /home/user/hello
hello MPI user: from process = 0 on machine=abacus0000, of NCPU=9 processes
hello MPI user: from process = 4 on machine=abacus0001, of NCPU=9 processes
hello MPI user: from process = 6 on machine=abacus0001, of NCPU=9 processes
hello MPI user: from process = 3 on machine=abacus0001, of NCPU=9 processes
hello MPI user: from process = 5 on machine=abacus0001, of NCPU=9 processes
hello MPI user: from process = 1 on machine=abacus0000, of NCPU=9 processes
hello MPI user: from process = 2 on machine=abacus0000, of NCPU=9 processes
hello MPI user: from process = 8 on machine=abacus0002, of NCPU=9 processes
hello MPI user: from process = 7 on machine=abacus0002, of NCPU=9 processes
O argumento -n aqui indica o número de processos que vai ser iniciado. Note que o programa a ser executado (nosso "hello") deve estar na linha de comando depois dos parâmetros -n e --hostfile, e que o caminho para ele deve ser absoluto.
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"))
Note que para usar os comandos acima (1) o número de processos deve ser menor ou igual ao máximo disponível pelo arquivo mpd.hosts, (2) a configuração --hostfile deve aparecer na linha de comando antes de -np. Para outras maneiras de criar clusters com o parallel veja as páginas de ajuda do pacote, especialmente a ajuda de "makeCluster".
Uma vez que você criou um "cluster" no R 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". Além disso, muitos pacotes do R que realizam rotinas demoradas têm facilidades para paralelizar, com o uso da biblioteca "parallel". As funções que permitem isso em geral têm um ou mais argumentos que informam o nome do "cluster" que você criou e que você quer usar para executar a função. As orientações estarão na páginas de ajuda das funções que permitem isso.