| | | |

Computação Paralela com C++

Ajude a manter o site livre, gratuito e sem propagandas. Colabore!

3.2 Comunicação ponto-a-ponto

Em computação distribuída, rotinas de comunicação entre as instâncias de processamento são utilizadas para o compartilhamento de dados. Comunicações ponto-a-ponto são aquelas que envolvem apenas duas instâncias de processamento.

3.2.1 Envio e recebimento síncronos

O envio e recebimento de dados entre duas instâncias de processamento pode ser feita com as rotinas MPI_Send e MPI_Recv. A primeira é utilizada para o envio de um dado a partir de uma instância de processamento e a segunda é utilizada para o recebimento de um dado em uma outra instância de processamento.

A sintaxe da MPI_Send é

1int MPI_Send(
2 const void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int dest,
6 int tag,
7 MPI_Comm comm)

e a sintaxe da MPI_Recv é

1int MPI_Recv(
2 void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int source,
6 int tag,
7 MPI_Comm comm,
8 MPI_Status *status)

O primeiro argumento é o ponteiro do buffer de dados. No caso do MPI_Send é o ponteiro para a posição da memória do dado a ser enviado. No caso do MPI_Recv é o ponteiro para a posição da memória do dado a ser recebido. O segundo argumento count é o número de dados sequenciais a serem enviados. O argumento datatype é o tipo de dado. O Open MPI suporta os seguintes tipos de dados

1MPI_SHORT short int
2MPI_INT int
3MPI_LONG long int
4MPI_LONG_LONG long long int
5MPI_UNSIGNED_CHAR unsigned char
6MPI_UNSIGNED_SHORT unsigned short int
7MPI_UNSIGNED unsigned int
8MPI_UNSIGNED_LONG unsigned long int
9MPI_UNSIGNED_LONG_LONG unsigned long long int
10MPI_FLOAT float
11MPI_DOUBLE double
12MPI_LONG_DOUBLE long double
13MPI_BYTE char

Ainda sobre as sintaxes acima, o argumento source é o identificador rank da instância de processamento. O argumento tag é um número arbitrário para identificar a operação de envio e recebimento. O argumento Comm especifica o comunicador (MPI_COMM_WORLD+ para aplicações básicas) e o último (somente para o MPI_Recv) fornece informação sobre o status do recebimento do dado.

Vamos estudar o seguinte código abaixo.

1#include <cstdio>
2
3// API MPI
4#include <mpi.h>
5
6int main (int argc, char** argv) {
7 // Inicializa o MPI
8 MPI_Init(NULL, NULL);
9
10 // numero total de processos
11 int world_size;
12 MPI_Comm_size(MPI_COMM_WORLD, &world_size);
13
14 // ID (rank) do processo
15 int world_rank;
16 MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
17
18 if (world_rank == 0) {
19 double x = 3.1416;
20 MPI_Send (&x, 1, MPI_DOUBLE, 1,
21 0, MPI_COMM_WORLD);
22 } else {
23 double y;
24 MPI_Recv (&y, 1, MPI_DOUBLE, 0,
25 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
26 printf ("Processo 1 recebeu o "\
27 "numero %f do processo 0.\n", y);
28 }
29
30
31 // Finaliza o MPI
32 MPI_Finalize ();
33
34 return 0;
35}

O código acima pode ser rodado com pelo menos duas instâncias de processamento. Nas linhas 20-21, o processo 0 envia o número 3.1416 (alocado na variável x) para o processo 1. Nas linhas 24-25, o processo 1 recebe o número enviado pelo processo 0 e o aloca na variável y.

Observação 3.2.1.(Sincronização)

As rotinas MPI_Send e MPI_Recv provocam a sincronização entre os processos envolvidos. Por exemplo, no código acima, no que o processo 0 atinge a rotina MPI_Send ele ficará aguardando o processo 1 receber todos os dados enviados e só, então, irá seguir adiante no código. Analogamente, no que o processo 1 atingir a rotina MPI_Recv, ele ficará aguardando o processo 0 enviar todos os dados e só, então, irá seguir adiante no código.

Envio e recebimento de array

As rotinas MPI_Send e MPI_Recv podem ser utilizadas para o envio e recebimento de arrays. A sintaxe é a mesma vista acima, sendo que o primeiro argumento *buf deve apontar para o início da array e o segundo argumento count corresponde ao tamanho da array.

Estudemos o seguinte código. Nele, o processo 0 aloca v=(0,1,2,3,4) e o processo 1 aloca w=(4,3,2,1,0). O processo 0 envia os valores v1,v2,v3 para o processo 1. Então, o processo 1 recebe estes valores e os aloca em w0,w1,w2. Desta forma, a saída impressa no terminal é

w=(1,2,3,1,0). (3.1)

Verifique!

1#include <iostream>
2#include <eigen3/Eigen/Dense>
3#include <mpi.h>
4
5int main()
6{
7 // Inicializa o MPI
8 MPI_Init(NULL, NULL);
9
10 // número total de processos
11 int world_size;
12 MPI_Comm_size(MPI_COMM_WORLD, &world_size);
13
14 // ID (rank) do processo
15 int world_rank;
16 MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
17
18 if (world_rank == 0)
19 {
20
21 Eigen::VectorXd v(5);
22 v << 0, 1, 2, 3, 4;
23
24 MPI_Send(v.data()+1, 3, MPI_DOUBLE, 1,
25 0, MPI_COMM_WORLD);
26 }
27 else
28 {
29 Eigen::VectorXd w(5);
30 w << 4, 3, 2, 1, 0;
31
32 MPI_Recv(w.data(), 3, MPI_DOUBLE, 0,
33 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
34 std::cout << w.transpose() << std::endl;
35 }
36
37 // Finaliza o MPI
38 MPI_Finalize();
39
40 return 0;
41}

3.2.2 Envio e recebimento assíncrono

O Open MPI também suporta rotinas MPI_Isend de envio e MPI_Irecv de recebimento assíncronos. Neste caso, o processo emissor envia o dado para outro processo e segue imediatamente a computação. O processo receptor deve conter uma rotina MPI_Irecv, mas também não aguarda sua conclusão para seguir a computação.

As sintaxes destas rotinas são semelhantes as das rotinas MPI_Send e MPI_Recv.

1int MPI_Isend(
2 const void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int dest,
6 int tag, MPI_Comm comm,
7 MPI_Request *request)
1int MPI_Irecv(
2 void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int source,
6 int tag,
7 MPI_Comm comm,
8 MPI_Request *request)

O último argumento permite verificar os envios e recebimentos.

Estudemos o seguinte código.

Código 9: IsendRecv.cpp
1// MPI_Status & MPI_Request
2MPI_Status status;
3MPI_Request request;
4
5if (world_rank == 0) {
6
7 double x = 3.1416;
8 MPI_Isend(&x, 1, MPI_DOUBLE, 1,
9 0, MPI_COMM_WORLD, &request);
10}
11else {
12
13 double y = 0.0;
14 MPI_Irecv(&y, 1, MPI_DOUBLE, 0,
15 0, MPI_COMM_WORLD, &request);
16
17 double x = y + 1.0;
18 printf("x = %f\n", x);
19
20 int recvd = 0;
21 while (!recvd)
22 MPI_Test(&request, &recvd, &status);
23
24 x = y + 1;
25 printf("x = %f\n", x);
26}
x = 1.000000
x = 4.141600

Neste código, MPI_Status e MPI_Request são alocados nas linhas 2 e 3, respectivamente. O Processo 0 faz uma requisição de envio do número 3.1416 para o processo 1, não aguarda o recebimento e segue adiante. O processo 1 tem uma rotina de requisição de recebimento não assíncrona na linha 14. Neste momento, ele não necessariamente recebe o dado enviado pelo processador (isto pode ocorrer a qualquer momento mais adiante). Na linha 17, o valor de y pode ainda ser 0.0. Consulte a saída do código!

Podemos verificar se uma requisição de envio (ou recebimento) foi completa usando a rotina MPI_Test. A sua sintaxe é:

1int MPI_Test(
2 MPI_Request *request,
3 int *flag,
4 MPI_Status *status)

O flag == 0 caso a requisição ainda não tenha sido completada e flag == 1 caso a requisição tenha sido executada.

No Código 9 acima, as linhas de código 20-22 são utilizadas para fazê-lo aguardar até que a requisição de recebimento seja completada. Desta forma, na linha 24 o valor de y é 3.1416 (o valor enviado pelo processo 0). Verifique!

Observação 3.2.2.(MPI_Wait)

No Código 9, as linhas 20-22 podem ser substituídas pela rotina MPI_Wait, que tem sintaxe:

1int MPI_Wait(
2 MPI_Request *request,
3 MPI_Status *status)

Verifique!

3.2.3 Exercícios

E. 3.2.1.

Faça um código C++/Open MPI para ser executado com 2 processadores. Um processo aloca x=0 e o outro processo aloca y=1. Logo, os processos trocam os valores, de forma que ao final o processo zero tem x=1 e o processo 1 tem y=0.

E. 3.2.2.

Faça um código C++/Open MPI para ser executado com 2 processadores. O processo 0 aloca um vetor de n1 elementos randômicos em ponto flutuante, envia o vetor para o processo 1. O processo 0, imprime no terminal a soma dos termos do vetor e o processo 1 imprime o produto dos termos do vetor.

E. 3.2.3.

Faça um código C++/Open MPI em que o processo 0 aloca um vetor de n1 elementos randômicos em ponto flutuante e envia o vetor para todos os demais processos (broadcast).

E. 3.2.4.

Faça um código C++/Open MPI em que o processo 0 aloca um vetor de n1 elementos randômicos em ponto flutuante e distribui pedaços do vetor para todos os demais processos (scatter). Todos os pedaços devem ter o mesmo tamanho e juntos devem compor o vetor original.

E. 3.2.5.

Faça um código C++/Open MPI em que todos os processos alocam um vetor de n1 elementos randômicos em ponto flutuante e o processo 0 recebe todos os vetores e os armazena em um único vetor grande, na ordem dos ranks dos processos (gather).

E. 3.2.6.

Faça um código C++/Open MPI para computar a média

1ni=0n1xi (3.2)

onde xi é um número em ponto flutuante e n1. Para a comunicação entre os processos, utilize apenas as rotinas MPI_Send e MPI_Recv.

  1. a)

    Fala para o caso de n divisível pelo número de processos.

  2. b)

    Fala para o caso de n não divisível pelo número de processos.

E. 3.2.7.

Faça um código C++/Open MPI para computação do produto interno entre dois vetores

x=(x0,x1,,xn), (3.3)
y=(y0,y1,,yn). (3.4)

Para a comunicação entre os processos, utilize apenas as rotinas MPI_Send e MPI_Recv. O processo 0 deve receber os resultados parciais dos demais processos e escrever na tela o valor computado do produto interno.

  1. a)

    Faça para o caso de n divisível pelo número de processos.

  2. b)

    Faça para o caso de n não divisível pelo número de processos.

E. 3.2.8.

Modifique o código do exercício anterior (Exercício 3.2.7) de forma a fazer a comunicação entre os processos com as rotinas MPI_Isend e MPI_Irecv. Há vantagem em utilizar estas rotinas? Se sim, quais?


Envie seu comentário

Aproveito para agradecer a todas/os que de forma assídua ou esporádica contribuem enviando correções, sugestões e críticas!

Opcional. Preencha seu nome para que eu possa lhe contatar.
Opcional. Preencha seu e-mail para que eu possa lhe contatar.
As informações preenchidas são enviadas por e-mail para o desenvolvedor do site e tratadas de forma privada. Consulte a política de uso de dados para mais informações.

Licença Creative Commons
Este texto é disponibilizado nos termos da Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional. Ícones e elementos gráficos podem estar sujeitos a condições adicionais.

Computação Paralela com C++

Ajude a manter o site livre, gratuito e sem propagandas. Colabore!

3.2 Comunicação ponto-a-ponto

Em computação distribuída, rotinas de comunicação entre as instâncias de processamento são utilizadas para o compartilhamento de dados. Comunicações ponto-a-ponto são aquelas que envolvem apenas duas instâncias de processamento.

3.2.1 Envio e recebimento síncronos

O envio e recebimento de dados entre duas instâncias de processamento pode ser feita com as rotinas MPI_Send e MPI_Recv. A primeira é utilizada para o envio de um dado a partir de uma instância de processamento e a segunda é utilizada para o recebimento de um dado em uma outra instância de processamento.

A sintaxe da MPI_Send é

1int MPI_Send(
2 const void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int dest,
6 int tag,
7 MPI_Comm comm)

e a sintaxe da MPI_Recv é

1int MPI_Recv(
2 void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int source,
6 int tag,
7 MPI_Comm comm,
8 MPI_Status *status)

O primeiro argumento é o ponteiro do buffer de dados. No caso do MPI_Send é o ponteiro para a posição da memória do dado a ser enviado. No caso do MPI_Recv é o ponteiro para a posição da memória do dado a ser recebido. O segundo argumento count é o número de dados sequenciais a serem enviados. O argumento datatype é o tipo de dado. O Open MPI suporta os seguintes tipos de dados

1MPI_SHORT short int
2MPI_INT int
3MPI_LONG long int
4MPI_LONG_LONG long long int
5MPI_UNSIGNED_CHAR unsigned char
6MPI_UNSIGNED_SHORT unsigned short int
7MPI_UNSIGNED unsigned int
8MPI_UNSIGNED_LONG unsigned long int
9MPI_UNSIGNED_LONG_LONG unsigned long long int
10MPI_FLOAT float
11MPI_DOUBLE double
12MPI_LONG_DOUBLE long double
13MPI_BYTE char

Ainda sobre as sintaxes acima, o argumento source é o identificador rank da instância de processamento. O argumento tag é um número arbitrário para identificar a operação de envio e recebimento. O argumento Comm especifica o comunicador (MPI_COMM_WORLD+ para aplicações básicas) e o último (somente para o MPI_Recv) fornece informação sobre o status do recebimento do dado.

Vamos estudar o seguinte código abaixo.

1#include <cstdio>
2
3// API MPI
4#include <mpi.h>
5
6int main (int argc, char** argv) {
7 // Inicializa o MPI
8 MPI_Init(NULL, NULL);
9
10 // numero total de processos
11 int world_size;
12 MPI_Comm_size(MPI_COMM_WORLD, &world_size);
13
14 // ID (rank) do processo
15 int world_rank;
16 MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
17
18 if (world_rank == 0) {
19 double x = 3.1416;
20 MPI_Send (&x, 1, MPI_DOUBLE, 1,
21 0, MPI_COMM_WORLD);
22 } else {
23 double y;
24 MPI_Recv (&y, 1, MPI_DOUBLE, 0,
25 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
26 printf ("Processo 1 recebeu o "\
27 "numero %f do processo 0.\n", y);
28 }
29
30
31 // Finaliza o MPI
32 MPI_Finalize ();
33
34 return 0;
35}

O código acima pode ser rodado com pelo menos duas instâncias de processamento. Nas linhas 20-21, o processo 0 envia o número 3.1416 (alocado na variável x) para o processo 1. Nas linhas 24-25, o processo 1 recebe o número enviado pelo processo 0 e o aloca na variável y.

Observação 3.2.1.(Sincronização)

As rotinas MPI_Send e MPI_Recv provocam a sincronização entre os processos envolvidos. Por exemplo, no código acima, no que o processo 0 atinge a rotina MPI_Send ele ficará aguardando o processo 1 receber todos os dados enviados e só, então, irá seguir adiante no código. Analogamente, no que o processo 1 atingir a rotina MPI_Recv, ele ficará aguardando o processo 0 enviar todos os dados e só, então, irá seguir adiante no código.

Envio e recebimento de array

As rotinas MPI_Send e MPI_Recv podem ser utilizadas para o envio e recebimento de arrays. A sintaxe é a mesma vista acima, sendo que o primeiro argumento *buf deve apontar para o início da array e o segundo argumento count corresponde ao tamanho da array.

Estudemos o seguinte código. Nele, o processo 0 aloca v=(0,1,2,3,4) e o processo 1 aloca w=(4,3,2,1,0). O processo 0 envia os valores v1,v2,v3 para o processo 1. Então, o processo 1 recebe estes valores e os aloca em w0,w1,w2. Desta forma, a saída impressa no terminal é

w=(1,2,3,1,0). (3.1)

Verifique!

1#include <iostream>
2#include <eigen3/Eigen/Dense>
3#include <mpi.h>
4
5int main()
6{
7 // Inicializa o MPI
8 MPI_Init(NULL, NULL);
9
10 // número total de processos
11 int world_size;
12 MPI_Comm_size(MPI_COMM_WORLD, &world_size);
13
14 // ID (rank) do processo
15 int world_rank;
16 MPI_Comm_rank(MPI_COMM_WORLD, &world_rank);
17
18 if (world_rank == 0)
19 {
20
21 Eigen::VectorXd v(5);
22 v << 0, 1, 2, 3, 4;
23
24 MPI_Send(v.data()+1, 3, MPI_DOUBLE, 1,
25 0, MPI_COMM_WORLD);
26 }
27 else
28 {
29 Eigen::VectorXd w(5);
30 w << 4, 3, 2, 1, 0;
31
32 MPI_Recv(w.data(), 3, MPI_DOUBLE, 0,
33 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
34 std::cout << w.transpose() << std::endl;
35 }
36
37 // Finaliza o MPI
38 MPI_Finalize();
39
40 return 0;
41}

3.2.2 Envio e recebimento assíncrono

O Open MPI também suporta rotinas MPI_Isend de envio e MPI_Irecv de recebimento assíncronos. Neste caso, o processo emissor envia o dado para outro processo e segue imediatamente a computação. O processo receptor deve conter uma rotina MPI_Irecv, mas também não aguarda sua conclusão para seguir a computação.

As sintaxes destas rotinas são semelhantes as das rotinas MPI_Send e MPI_Recv.

1int MPI_Isend(
2 const void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int dest,
6 int tag, MPI_Comm comm,
7 MPI_Request *request)
1int MPI_Irecv(
2 void *buf,
3 int count,
4 MPI_Datatype datatype,
5 int source,
6 int tag,
7 MPI_Comm comm,
8 MPI_Request *request)

O último argumento permite verificar os envios e recebimentos.

Estudemos o seguinte código.

Código 9: IsendRecv.cpp
1// MPI_Status & MPI_Request
2MPI_Status status;
3MPI_Request request;
4
5if (world_rank == 0) {
6
7 double x = 3.1416;
8 MPI_Isend(&x, 1, MPI_DOUBLE, 1,
9 0, MPI_COMM_WORLD, &request);
10}
11else {
12
13 double y = 0.0;
14 MPI_Irecv(&y, 1, MPI_DOUBLE, 0,
15 0, MPI_COMM_WORLD, &request);
16
17 double x = y + 1.0;
18 printf("x = %f\n", x);
19
20 int recvd = 0;
21 while (!recvd)
22 MPI_Test(&request, &recvd, &status);
23
24 x = y + 1;
25 printf("x = %f\n", x);
26}
x = 1.000000
x = 4.141600

Neste código, MPI_Status e MPI_Request são alocados nas linhas 2 e 3, respectivamente. O Processo 0 faz uma requisição de envio do número 3.1416 para o processo 1, não aguarda o recebimento e segue adiante. O processo 1 tem uma rotina de requisição de recebimento não assíncrona na linha 14. Neste momento, ele não necessariamente recebe o dado enviado pelo processador (isto pode ocorrer a qualquer momento mais adiante). Na linha 17, o valor de y pode ainda ser 0.0. Consulte a saída do código!

Podemos verificar se uma requisição de envio (ou recebimento) foi completa usando a rotina MPI_Test. A sua sintaxe é:

1int MPI_Test(
2 MPI_Request *request,
3 int *flag,
4 MPI_Status *status)

O flag == 0 caso a requisição ainda não tenha sido completada e flag == 1 caso a requisição tenha sido executada.

No Código 9 acima, as linhas de código 20-22 são utilizadas para fazê-lo aguardar até que a requisição de recebimento seja completada. Desta forma, na linha 24 o valor de y é 3.1416 (o valor enviado pelo processo 0). Verifique!

Observação 3.2.2.(MPI_Wait)

No Código 9, as linhas 20-22 podem ser substituídas pela rotina MPI_Wait, que tem sintaxe:

1int MPI_Wait(
2 MPI_Request *request,
3 MPI_Status *status)

Verifique!

3.2.3 Exercícios

E. 3.2.1.

Faça um código C++/Open MPI para ser executado com 2 processadores. Um processo aloca x=0 e o outro processo aloca y=1. Logo, os processos trocam os valores, de forma que ao final o processo zero tem x=1 e o processo 1 tem y=0.

E. 3.2.2.

Faça um código C++/Open MPI para ser executado com 2 processadores. O processo 0 aloca um vetor de n1 elementos randômicos em ponto flutuante, envia o vetor para o processo 1. O processo 0, imprime no terminal a soma dos termos do vetor e o processo 1 imprime o produto dos termos do vetor.

E. 3.2.3.

Faça um código C++/Open MPI em que o processo 0 aloca um vetor de n1 elementos randômicos em ponto flutuante e envia o vetor para todos os demais processos (broadcast).

E. 3.2.4.

Faça um código C++/Open MPI em que o processo 0 aloca um vetor de n1 elementos randômicos em ponto flutuante e distribui pedaços do vetor para todos os demais processos (scatter). Todos os pedaços devem ter o mesmo tamanho e juntos devem compor o vetor original.

E. 3.2.5.

Faça um código C++/Open MPI em que todos os processos alocam um vetor de n1 elementos randômicos em ponto flutuante e o processo 0 recebe todos os vetores e os armazena em um único vetor grande, na ordem dos ranks dos processos (gather).

E. 3.2.6.

Faça um código C++/Open MPI para computar a média

1ni=0n1xi (3.2)

onde xi é um número em ponto flutuante e n1. Para a comunicação entre os processos, utilize apenas as rotinas MPI_Send e MPI_Recv.

  1. a)

    Fala para o caso de n divisível pelo número de processos.

  2. b)

    Fala para o caso de n não divisível pelo número de processos.

E. 3.2.7.

Faça um código C++/Open MPI para computação do produto interno entre dois vetores

x=(x0,x1,,xn), (3.3)
y=(y0,y1,,yn). (3.4)

Para a comunicação entre os processos, utilize apenas as rotinas MPI_Send e MPI_Recv. O processo 0 deve receber os resultados parciais dos demais processos e escrever na tela o valor computado do produto interno.

  1. a)

    Faça para o caso de n divisível pelo número de processos.

  2. b)

    Faça para o caso de n não divisível pelo número de processos.

E. 3.2.8.

Modifique o código do exercício anterior (Exercício 3.2.7) de forma a fazer a comunicação entre os processos com as rotinas MPI_Isend e MPI_Irecv. Há vantagem em utilizar estas rotinas? Se sim, quais?


Envie seu comentário

Aproveito para agradecer a todas/os que de forma assídua ou esporádica contribuem enviando correções, sugestões e críticas!

Opcional. Preencha seu nome para que eu possa lhe contatar.
Opcional. Preencha seu e-mail para que eu possa lhe contatar.
As informações preenchidas são enviadas por e-mail para o desenvolvedor do site e tratadas de forma privada. Consulte a política de uso de dados para mais informações.

Licença Creative Commons
Este texto é disponibilizado nos termos da Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional. Ícones e elementos gráficos podem estar sujeitos a condições adicionais.

Pedro H A Konzen
| | | |