Comunicação Ponto a Ponto
● Transferência de dados entre processos
específicos, que não se encaixam em um padrão global prédefinido;
● Sempre ocorre entre dois processos, um processo que envia e outro que recebe a mensagem;
● Como sempre no MPI, o processo que envia é quem define os parâmetros da mensagem:
o que e para quem;
● Há muitas e muitas variações com semântica distinta;
MPI_SEND
● Operação “bloqueante”;
● Envia mensagem para um destinatário específico;
● Mensagem é encapsulada em um
“envelope”;
– Endereço do remetente (implícito);
– Endereço do destinatário;
– Etiqueta;
– Comunicador (implícito no C++);
MPI_SEND
C:
int MPI_Send(void* buf, int count, MPI_Datatype datatype,
int dest, int tag, MPI_Comm comm) Fortran:
MPI_SEND(BUF, COUNT, DATATYPE,
DEST, TAG, COMM, IERROR)
<type> BUF(*)
INTEGER COUNT, DATATYPE, DEST INTEGER TAG, COMM, IERROR
MPI_SEND
C++:
MPI::Comm::Send(
const void* buf, int count,
const MPI::Datatype& datatype, int dest, int tag) const
MPI_RECV
C:
int MPI_Recv(void* buf, int count,
MPI_Datatype datatype, int source, int tag, MPI_Comm comm,
MPI_Status *status)
MPI_RECV
Fortran:
MPI_RECV(BUF, COUNT, DATATYPE,
SOURCE, TAG, COMM, STATUS, IERROR)
<type> BUF(*)
INTEGER COUNT, DATATYPE, SOURCE, INTEGER TAG, COMM
INTEGER STATUS(MPI_STATUS_SIZE), IERROR
MPI_RECV
C++:
void MPI::Comm::Recv(void* buf, int count, const MPI::Datatype& datatype,
int source, int tag,
MPI::Status& status) const
void MPI::Comm::Recv(void* buf, int count, const MPI::Datatype& datatype,
int source, int tag) const
MPI_RECV - status
C:
MPI_Status stat;
stat.MPI_SOURCE;
stat.MPI_TAG;
stat.MPI_ERROR;
Fortran:
MPI_STATUS stat(3) stat(MPI_SOURCE) stat(MPI_TAG)
stat(MPI_ERROR)
C++:
MPI::Status stat;
stat.Get_source();
stat.Set_source();
stat.Get_tag();
stat.Set_tag();
stat.Get_error();
stat.Set_error();
MPI_RECV - source
● Seleciona qual o processador de origem que esta chamada deve receber;
● É possível não especificar um processador específico usando MPI_ANY_SOURCE;
● Todos os apenas um, não é possível especificar um subconjunto diferente destes (outro comunicador ou outra etiqueta);
ETIQUETAS “TAGS”
● As rotinas de recepção podem usar as
etiquetas para selecionar e discriminar as mensagens que devem receber;
● Ex: mensagens que carregam dados e mensagens de terminação;
● É apenas um número inteiro, entre 0 e MPI::UB;
● Podese receber qualquer mensagem com MPI_ANY_TAG;
MPI_GET_COUNT
● Número de elementos recebidos fica armazenado em “stat”;
● Não pode ser acessado diretamente, mas necessita chamada explícita (já é assim em C++);
● Por motivos de eficiência da
implementação, o tipo de dados é necessário na chamada;
MPI_GET_COUNT
● Número de elementos em “stat”, mas necessita chamada explícita:
C:int MPI_Get_count(MPI_Status *status, MPI_Datatype datatype, int *count) Fortran:
MPI_GET_COUNT(STATUS, DATATYPE, COUNT, IERROR)
INTEGER STATUS(MPI_STATUS_SIZE) INTEGER DATATYPE, COUNT, IERROR
MPI_GET_COUNT
C++:
int MPI::Status::Get_count(
const MPI::Datatype& datatype) const
IGNORANDO STATUS
● Há um custo em processar o status das mensagens, o que às vezes é
desnecessário;
● Podese evitar este custo com MPI_STATUS_IGNORE e
MPI_STATUSES_IGNORE em C e Fortran;
● Em C++ simplesmente usase a função sobrecarregada sem status;
Comunicação MPI com
Buffer
Modos de Comunicação
● Quatro modos:
– Padrão: MPI decide se/quanto buffer usar;
– “Buffered”: o usuário fornece o buffer para uso do MPI;
– Síncrono: completa quando a mensagem começa a chegar no destino;
– Pronto (“Ready”): só pode enviar se
necessariamente já tiver sido chamada a rotina de recepção correspondente;
Modos de Comunicação
● Há rotinas de envio para cada tipo de modo, com nomes diferentes, mas a mesma assinatura:
– MPI_SEND;
– MPI_BSEND;
– MPI_SSEND;
– MPI_RSEND;
● Há apenas uma rotina de recepção, que funciona para qualquer modo:
– MPI_RECV;
Modo Padrão
● É um modo não local;
● O envio pode ser requisitado sem que a recepção tenha sido;
● Como o sistema decide, não é possível
garantir que o programa irá funcionar em qualquer situação;
● “Deadlock” é possível;
● Programas “normais” são escritos no modo padrão, mas isto não é portátil;
Modo “Buffered”
● O usuário fornece ao sistema MPI espaço para usar como buffer;
● O MPI usa este espaço para copiar as
mensagens de saída, e as rotinas de envio retornam imediatamente;
● É um modo local;
● Se o espaço for insuficiente, um erro ocorre e é sinalizado;
MPI_BUFFER_ATTACH
● Associa um buffer com o MPI;
● Apenas um buffer pode ser associado;
● Pode não ser fácil estimar o tamanho
necessário para um programa complexo;
● Só mensagens enviadas no modo buffered usam este buffer;
MPI_BUFFER_ATTACH
C:
int MPI_Buffer_attach(
void* buffer, int size) Fortran:
MPI_BUFFER_ATTACH(BUFFER, SIZE, IERROR) <type> BUFFER(*)
INTEGER SIZE, IERROR C++:
void MPI::Attach_buffer(
void* buffer, int size)
MPI_BUFFER_DETACH
● Desassocia o buffer fornecido pelo usuário do MPI;
● Necessário antes de liberar a memória!
MPI_BUFFER_DETACH
C:int MPI_Buffer_detach(
void* buffer_addr, int* size) Fortran:
MPI_BUFFER_DETACH(
BUFFER_ADDR, SIZE, IERROR) <type> BUFFER_ADDR(*)
INTEGER SIZE, IERROR
C++:int MPI::Detach_buffer(void*& buffer)
Modo Síncrono
● O envio só retorna quando a mensagem começa a ser recebida;
● É um modo não local;
● Se funcionar, funciona funciona sempre, e os programas são realmente portáteis!
● Conhecido como o modo seguro, pois independe do tamanho dos buffers internos do MPI;
Modo Pronto
● É necessário garantir via programação que a função de recepção seja chamada antes da função de envio correspondente;
● Isto pode permitir que a comunicação seja mais eficiente;
● É claramente não local;
Propriedades da
Comunicação Ponto a Ponto
● Não há ultrapassagem;
● Não há “justiça”;
● Recursos são limitados e sua exaustão causa erros;
● Um programa que funciona na ausência de buffers é um programa seguro;
Comunicação Não Bloqueante
● Permite que a comunicação ocorra simultaneamente com a computação;
● Pode esconder a latência e os custos de comunicação;
● Pode tornar os programas mais eficientes;
● Comunicações divididas em duas etapas:
– Início;
– Teste para término;
Comunicação Não Bloqueante
● Envio e recepção podem ser não bloqueantes;
● Uma rotina não bloqueante pode ter na
outra ponta uma rotina bloqueante ou não bloqueante;
● As rotinas de envio não bloqueante podem ter os quatro modos: padrão, “buffered”, síncrono e pronto;
Comunicação Não Bloqueante
● As rotinas de envio são sempre locais, e retornam imediatamente;
● O envio, no entanto, e as funções que testam a comunicação, funcionam
segundo o modo;
● As rotinas de envio e teste de término são conectadas por objetos do tipo
MPI::Request;
Comunicação Não Bloqueante
● As rotinas de envio não bloqueante tem nomes baseados nas rotinas bloqueantes:
– MPI_ISEND;
– MPI_IBSEND;
– MPI_ISSEND;
– MPI_IRSEND;
● A rotina de recepção não bloqueante é chamada:
– MPI_IRECV;
Comunicação Não Bloqueante
● A comunicação é completada com uma das duas rotinas (ou suas derivadas):
● MPI_WAIT;
● MPI_WAITSOME;
● MPI_WAITALL;
● MPI_WAITANY;
● MPI_TEST;
● MPI_TESTSOME;
● MPI_TESTALL;
● MPI_TESTANY;
MPI_ISEND
C:
int MPI_Isend(
void* buf, int count, MPI_Datatype datatype, int dest,
int tag, MPI_Comm comm, MPI_Request *request)
MPI_ISEND
Fortran:
MPI_ISEND(BUF, COUNT, DATATYPE, DEST, TAG, COMM,
REQUEST, IERROR) <type> BUF(*)
INTEGER COUNT, DATATYPE, DEST
INTEGER TAG, COMM, REQUEST, IERROR
MPI_ISEND
C++:
MPI::Request MPI::Comm::Isend(
const void* buf, int count,
const MPI::Datatype& datatype, int dest,
int tag) const
MPI_I[BSR]SEND
● Análogos, apenas retornam (ou
acrescentam para C e Fortran) um objeto do tipo MPI_REQUEST;
MPI_IRECV
C:int MPI_Irecv(
void* buf, int count,
MPI_Datatype datatype, int source,
int tag,
MPI_Comm comm,
MPI_Request *request)
MPI_IRECV
Fortran:
MPI_IRECV(
BUF, COUNT, DATATYPE,
SOURCE, TAG, COMM,REQUEST, IERROR)
<type> BUF(*)
INTEGER COUNT, DATATYPE INTEGER SOURCE, TAG, COMM INTEGER REQUEST, IERROR
MPI_IRECV
C++:
MPI::Request MPI::Comm::Irecv(
void* buf, int count,
const MPI::Datatype& datatype, int source, int tag) const
MPI_WAIT
● Espera até que a comunicação “complete”;
● O que é “completar”, depende do modo:
– Padrão: mensagem foi copiada para um buffer local;
– Síncrono: mensagem começou a ser copiada para o buffer de recepção;
– Etc.;
● Esta chamada desaloca o objeto MPI_REQUEST criado!;
MPI_TEST
● Também é possível verificar o resultado de uma comunicação não bloqueante com
MPI_TEST;
● Retorna imediatamente, é uma operação local;
● DESALOCA objeto Request!
● Não deve ser usada para “busy waiting!”
● O objeto request deve ser liberado
explicitamente com MPI_REQUEST_FREE;
MPI_TEST
C:
int MPI_Test(
MPI_Request *request, int *flag,
MPI_Status *status) Fortran:
MPI_TEST(REQUEST, FLAG, STATUS, IERROR) LOGICAL FLAG
INTEGER REQUEST
INTEGER STATUS(MPI_STATUS_SIZE) INTEGER IERROR
MPI_TEST
C++:
bool MPI::Request::Test(
MPI::Status& status) bool MPI::Request::Test()
MPI_WAIT
C:
int MPI_Wait(
MPI_Request *request, MPI_Status *status) Fortran:
MPI_WAIT(REQUEST, STATUS, IERROR) INTEGER REQUEST
INTEGER STATUS(MPI_STATUS_SIZE), IERROR
MPI_WAIT
C++:
void MPI::Request::Wait(
MPI::Status& status) void MPI::Request::Wait()
Testes Múltiplos
● Muitas vezes é conveniente iniciar várias comunicações, e verificar qual delas
completou, em qualquer ordem;
● Podese fazer isto com um vetor de MPI_REQUEST, e com as funções:
– MPI_WAITANY
– MPI_WAITSOME
– MPI_WAITALL
MPI_WAITANY
C:
int MPI_Waitany(
int count,
MPI_Request *array_of_requests, int *index,
MPI_Status *status)
MPI_WAITANY
Fortran:
MPI_WAITANY(
COUNT, ARRAY_OF_REQUESTS, INDEX, STATUS, IERROR)
INTEGER COUNT, ARRAY_OF_REQUESTS(*)
INTEGER INDEX, STATUS(MPI_STATUS_SIZE) IERROR
MPI_WAITANY
C++:
static int MPI::Request::Waitany(
int count,
MPI::Request array_of_requests[], MPI::Status& status)
static int MPI::Request::Waitany(
int count,
MPI::Request array_of_requests[])
MPI_TESTANY
● Análogo a MPI_WAITANY;
● Análogo a MPI_TEST;
MPI_TESTANY
C:int MPI_Testany(int count,
MPI_Request *array_of_requests, int *index,
int *flag,
MPI_Status *status)
MPI_TESTANY
Fortran:
MPI_TESTANY(COUNT,
ARRAY_OF_REQUESTS, INDEX, FLAG, STATUS, IERROR)
LOGICAL FLAG INTEGER COUNT,
INTEGER ARRAY_OF_REQUESTS(*)
INTEGER INDEX, STATUS(MPI_STATUS_SIZE) INTEGER IERROR
MPI_TESTANY
C++:
static bool MPI::Request::Testany(
int count,
MPI::Request array_of_requests[], int& index,
MPI::Status& status)
static bool MPI::Request::Testany(
int count,
MPI::Request array_of_requests[], int& index)
MPI_WAITALL
● Obviamente, espera até que todas as
comunicações pendentes informadas a ela completem;
MPI_WAITALL
C:
int MPI_Waitall(
int count,
MPI_Request *array_of_requests, MPI_Status *array_of_statuses)
MPI_WAITALL
Fortran:
MPI_WAITALL(COUNT,
ARRAY_OF_REQUESTS,
ARRAY_OF_STATUSES, IERROR)
INTEGER COUNT, ARRAY_OF_REQUESTS(*)
INTEGER ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*) INTEGER IERROR
MPI_WAITALL
C++:static void MPI::Request::Waitall(
int count,
MPI::Request array_of_requests[], MPI::Status array_of_statuses[]) static void MPI::Request::Waitall(
int count,
MPI::Request array_of_requests[])
MPI_TESTALL
● Análogo a MPI_WAITALL;
● Análogo a MPI_TEST;
MPI_TESTALL
C:
int MPI_Testall(int count,
MPI_Request *array_of_requests, int *flag,
MPI_Status *array_of_statuses)
MPI_TESTALL
Fortran:
MPI_TESTALL(COUNT,
ARRAY_OF_REQUESTS,
FLAG, ARRAY_OF_STATUSES, IERROR)
LOGICAL FLAG
INTEGER COUNT, ARRAY_OF_REQUESTS(*)
INTEGER ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*) INTEGER IERROR
MPI_TESTALL
C++:
static bool MPI::Request::Testall(
int count,
MPI::Request array_of_requests[], MPI::Status array_of_statuses[]) static bool MPI::Request::Testall(
int count,
MPI::Request array_of_requests[])
MPI_WAIT_SOME
● Espera até que pelo menos uma das comunicações pendentes tenham
terminado;
● Retorna um contagem e um vetor de índices de comunicações completas;
● Desaloca o “request” correspondente;
● Normalmente, é melhor usar esta do que MPI_WAIT_ANY;
MPI_WAIT_SOME
C:
int MPI_Waitsome(
int incount,
MPI_Request *array_of_requests, int *outcount,
int *array_of_indices,
MPI_Status *array_of_statuses)
MPI_WAIT_SOME
Fortran:
MPI_WAITSOME(INCOUNT,
ARRAY_OF_REQUESTS, OUTCOUNT, ARRAY_OF_INDICES,
ARRAY_OF_STATUSES, IERROR)
INTEGER INCOUNT, ARRAY_OF_REQUESTS(*) INTEGER OUTCOUNT, ARRAY_OF_INDICES(*)
INTEGER ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*) INTEGER IERROR
MPI_WAIT_SOME
C++:
static int MPI::Request::Waitsome(int incount, MPI::Request array_of_requests[],
int array_of_indices[],
MPI::Status array_of_statuses[])
static int MPI::Request::Waitsome(int incount, MPI::Request array_of_requests[],
int array_of_indices[])
MPI_TEST_SOME
● Análogo a MPI_WAIT_SOME e MPI_TEST_ANY;
● Retorna imediatamente;
● Normalmente, é melhor usar esta do que MPI_TEST_ANY;
MPI_TEST_SOME
C:
int MPI_Testsome(int incount,
MPI_Request *array_of_requests, int *outcount,
int *array_of_indices,
MPI_Status *array_of_statuses)
MPI_TEST_SOME
Fortran:
MPI_TESTSOME(INCOUNT,
ARRAY_OF_REQUESTS, OUTCOUNT, ARRAY_OF_INDICES,
ARRAY_OF_STATUSES, IERROR)
INTEGER INCOUNT, OUTCOUNT, IERROR INTEGER ARRAY_OF_REQUESTS(*)
INTEGER ARRAY_OF_INDICES(*),
INTEGER ARRAY_OF_STATUSES(MPI_STATUS_SIZE,*)
MPI_TEST_SOME
C++:
static int MPI::Request::Testsome(
int incount,
MPI::Request array_of_requests[], int array_of_indices[],
MPI::Status array_of_statuses[]) static int MPI::Request::Testsome(
int incount,
MPI::Request array_of_requests[], int array_of_indices[])
MPI_REQUES_GET_STATUS
● Com MPI_REQUEST_GET_STATUS é possível verificar se uma comunicação completou, sem invalidar o “request”;
● O mesmo request pode ser reusado por outras funções;
● Útil no caso do desenvolvimento de bibliotecas de comunicação;
MPI_PROBE – MPI_IPROBE
● É possível verificar se uma mensagem está para chegar, sem recebêla efetivamente;
● É possível portanto alocar buffers para mensagens antes de recebêlas;
● MPI_PROBE bloqueia até que uma
mensagem específica esteja preparada para chegar;
● MPI_IPROBE retorna imediatamente,
indicando se há uma mensagem específica pronta para chegar;
MPI_PROBE
C:int MPI_Probe(
int source, int tag,
MPI_Comm comm,
MPI_Status *status)
MPI_PROBE
Fortran:
MPI_PROBE(
SOURCE, TAG, COMM, STATUS, IERROR)
INTEGER SOURCE, TAG, COMM, IERROR INTEGER STATUS(MPI_STATUS_SIZE)
MPI_PROBE
C++:
void MPI::Comm::Probe(int source,
int tag, MPI::Status& status) const void MPI::Comm::Probe(int source,
int tag) const
MPI_IPROBE
C:
int MPI_Iprobe(
int source, int tag,
MPI_Comm comm, int *flag,
MPI_Status *status)
MPI_IPROBE
Fortran:
MPI_IPROBE(SOURCE, TAG, COMM, FLAG, STATUS, IERROR)
LOGICAL FLAG
INTEGER SOURCE, TAG, COMM, IERROR INTEGER STATUS(MPI_STATUS_SIZE)
MPI_IPROBE
C++:
bool MPI::Comm::Iprobe(
int source, int tag,
MPI::Status& status) const bool MPI::Comm::Iprobe(
int source,
int tag) const
MPI_CANCEL
● É possível cancelar uma comunicação pendente com MPI_CANCEL;
● Ainda é necessário completar a
comunicação com MPI_WAIT, MPI_TEST, etc!
● O cancelamento da comunicação é indicado no status;
● Não é garantido que o cancelamento ocorra!
● Para ocasiões muito especiais apenas;
MPI_CANCEL
C:int MPI_Cancel(MPI_Request *request)
Fortran:
MPI_CANCEL(REQUEST, IERROR) INTEGER REQUEST, IERROR
C++:
void MPI::Request::Cancel() const
MPI_TEST_CANCELLED
C:
int MPI_Test_cancelled(
MPI_Status *status, int *flag) Fortran:
MPI_TEST_CANCELLED(STATUS, FLAG, IERROR) LOGICAL FLAG
INTEGER STATUS(MPI_STATUS_SIZE), IERROR C++:
bool MPI::Status::Is_cancelled() const
MPI_SENDRECV
● Padrão típico: troca com vizinhos;
● Recebe do anterior, envia para o próximo;
● Pode ser feito com chamadas bloqueantes e cuidado;
● Pode ser feito com chamadas não bloqueantes, e menos cuidado;
● MPI_SEND_RECV cuida dos detalhes e não há risco de deadlock;
● Operação bloqueante, que retorna quando ambas as operações estiverem completas;
MPI_SENDRECV
C:
int MPI_Sendrecv(
void *sendbuf, int sendcount, MPI_Datatype sendtype,
int dest, int sendtag,
void *recvbuf, int recvcount, MPI_Datatype recvtype,
int source, int recvtag,
MPI_Comm comm, MPI_Status *status)
MPI_SENDRECV
Fortran:
MPI_SENDRECV(SENDBUF, SENDCOUNT, SENDTYPE, DEST, SENDTAG,
RECVBUF, RECVCOUNT, RECVTYPE,
SOURCE, RECVTAG, COMM, STATUS, IERROR) <type> SENDBUF(*), RECVBUF(*)
INTEGER SENDCOUNT, SENDTYPE, DEST
INTEGER SENDTAG, RECVCOUNT, RECVTYPE INTEGER SOURCE, RECVTAG, COMM
INTEGER STATUS(MPI_STATUS_SIZE), IERROR
MPI_SENDRECV
C++:
void MPI::Comm::Sendrecv(
const void *sendbuf, int sendcount, const MPI::Datatype& sendtype,
int dest, int sendtag,
void *recvbuf, int recvcount, const MPI::Datatype& recvtype, int source, int recvtag,
MPI::Status& status) const
MPI_SENDRECV
C++:
void MPI::Comm::Sendrecv(
const void *sendbuf, int sendcount, const MPI::Datatype& sendtype,
int dest, int sendtag,
void *recvbuf, int recvcount, const MPI::Datatype& recvtype, int source, int recvtag) const
MPI_SENDRECV_REPLACE
● Análogo a MPI_SENDRECV, com um único buffer para envio e recepção;
● Mensagem que chega substitui a mensagem enviada;
● Operação bloqueante!
MPI_SENDRECV_REPLACE
C:
int MPI_Sendrecv_replace(
void* buf, int count, MPI_Datatype datatype, int dest, int sendtag,
int source, int recvtag, MPI_Comm comm,
MPI_Status *status)
MPI_SENDRECV_REPLACE
Fortran:
MPI_SENDRECV_REPLACE(BUF, COUNT, DATATYPE, DEST, SENDTAG,
SOURCE, RECVTAG,
COMM, STATUS, IERROR) <type> BUF(*)
INTEGER COUNT, DATATYPE, DEST, IERROR INTEGER SENDTAG, SOURCE, RECVTAG, COMM INTEGER STATUS(MPI_STATUS_SIZE)
MPI_SENDRECV_REPLACE
C++:
void MPI::Comm::Sendrecv_replace(
void* buf, int count,
const MPI::Datatype& datatype, int dest, int sendtag,
int source, int recvtag, MPI::Status& status) const
MPI_SENDRECV_REPLACE
C++:
void MPI::Comm::Sendrecv_replace(
void* buf, int count,
const MPI::Datatype& datatype, int dest, int sendtag,
int source, int recvtag) const