Slides from the talk I did at OpenSolaris Day in Porto Alegre.
Download: hpc_and_OpenSolaris.odp
MPI é a sigla para Message Passing Interface, um padrão de comunicação de dados para computação paralela. O MPI oferece diversas abstracções que facilitam e padronizam o desenvolvimento de aplicações paralelas. Por exemplo, você pode programar para vários processadores, nós de um cluster, supercomputadores ou Internet utilizando a mesma infraestrutura transparentemente.
Cluster Columbia da NASA, com 1024 nós.
Como MPI é um padrão, existem vários padrões de implementação, abertas, fechadas, comerciais ou gratuitas. MPI é definido a princÃpio para C e Fortran, mas há implementações em outras linguagens como Java ou Python, por exemplo. A implementação que eu vou utilizar nesse exemplo é a OpenMPI.
A notÃcia boa é que você não precisa ter um supercomputador em casa para aprender e praticar computação paralela, uma máquina doméstica serve. Se você tiver uma máquina com múltiplos processadores, melhor ainda.
Para instalar um ambiente de desenvolvimento para MPI no Ubuntu Linux basta um comando:
sudo apt-get install build-essential openmpi-dev
Isso vai instalar um conjunto básico de compiladores e o ambiente OpenMPI.
Vamos criar um arquivo chamado ola.c com o conteúdo:
#include
#include
int size, rank;
int main(int argc, char *argv[]){
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&size);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
printf("Oi. Eu sou o processo %d de %d\n", rank, size);
MPI_Finalize();
}
Para compilar esse código vamos usar o comando mpicc que foi instalado junto com o pacote openmpi-dev. Ele é uma interface para o gcc, e vai cuidar de toda a linkagem com as bibliotecas do MPI. Você pode usar os parâmetros do gcc com o mpicc.
mpicc ola.c -o ola
Se tudo der certo esse comando vai criar o binário ola.
Outra ferramenta importante é o mpirun, que levantar o mpi nos diversos nós e mandar cada nó executar o binário. O mpirun não precisa de um programa mpi para rodar, por exemplo, se dermos esse comando:
mpirun -np 4 echo oi
Você vai ter essa saÃda:
oi
oi
oi
oi
Você mandou 4 nós (-np 4) executar o comando echo oi (imprime oi). Para mandar 5 nós executarem nosso binário ola:
mpirun -np 5 ola
E vamos ter uma saÃda mais ou menos assim:
Oi. Eu sou o processo 1 de 5
Oi. Eu sou o processo 4 de 5
Oi. Eu sou o processo 0 de 5
Oi. Eu sou o processo 2 de 5
Oi. Eu sou o processo 3 de 5
Por que as saÃdas sairam desordenadas? Porque elas rodaram em paralelo e não temos como saber qual foi sua ordem de execução. Assim cada nó entrou no printf em um momento diferente e imprimiu seu rank e seu size naquele momento. Você pode experimentar usar o parâmetro -np com outros números maiores ou menores que 5.
Até aqui não há muita graça porque não há troca de mensagens. Há muito o que se dizer sobre como trocar mensagens do MPI mas a maneira mais fácil de se começar é com a função mpi_send.
Vamos fazer um programa bem simples onde o nó 0 vai mandar uma mensagem para o nó 1. A mensagem vai ser um número, 42. Criemos um arquivo chamado msg.c com o código:
#include
#include
int size, rank, msg, source, dest, tag;
int main(int argc, char *argv[]){
MPI_Status stat;
MPI_Init(&argc,&argv);
MPI_Comm_size(MPI_COMM_WORLD,&size);
MPI_Comm_rank(MPI_COMM_WORLD,&rank);
if(rank==0){
msg = 42; dest = 1; tag = 0;
MPI_Send(&msg, 1, MPI_INT, dest, tag, MPI_COMM_WORLD);
printf("Processo %d enviou %d para %d.\n", rank, msg, dest);
}
if(rank==1){
source = 0; tag = 0;
MPI_Recv(&msg, 1, MPI_INT, source, tag, MPI_COMM_WORLD, &stat);
printf("Processo %d recebeu %d de %d.\n", rank, msg, source);
}
MPI_Finalize();
}
No processo de rank 0 vamos enviar o conteúdo da variável inteira msg para o processo de rank 1. Note que no processo de rank 1, o valor de msg não está definido. O comando MPI_Send vai receber 6 parâmetros.
int MPI_Send( void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm)
Do outro lado, no processo 1 vamos usar o MPI_recv, que recebe 7 parâmetros.
int MPI_Recv( void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Status *status)
Para compilar esse exemplo usamos novamente o mpicc.
mpicc msg.c -o msg
E para executa-lo o mpirun.
mpirun -np 2 msg
O programa vai escrever essa mensagem:
Processo 0 enviou 42 para 1.
Processo 1 recebeu 42 de 0
No processo 1 a msg estava inicialmente vazia e no processo 0 havia 42, mas depois do MPI_recv o processo 1 pode escrever o conteúdo 42 de msg. Logo, houve comunicação.
Por um problema no empacotamento do mpich no Ubuntu toda vez que você executa o MPI você recebe umas mensagens horrorosas de erro, que na verdade são só um aviso que ele não encontrou uma placa de rede Infiniband.
Para você silenciar na unha essa chatice use o mpirun assim:
mpiexec –mca btl ^openib -np 1 executável
Onde -np 1 deve ser substituido pelo seu número de processos e executável pelo seu executável.
Outra dica é que você pode utilizar uma distribuição Linux que já venha com o MPI instalado. Por exemplo o Scientific Linux ou o Parallel Knoppix.