| | | |

Computação Paralela com C++

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

3.3 Comunicações coletivas

Rotinas MPI de comunicações coletivas são aquelas que envolvem múltiplas instâncias de processamento. Elas são utilizadas para a troca de dados entre processos, onde um ou mais processos enviam dados para todos os demais processos ou recebem dados de todos os demais processos.

3.3.1 Barreira de sincronização

Podemos forçar a sincronização de todos os processos em um determinado ponto do código utilizando a rotina de sincronização MPI_Barrier. Sua sintaxe é a seguinte

1int MPI_Barrier (MPI_Comm comm)

Quando um processo encontra esta rotina ele aguarda todos os demais processos. No momento em que todos os processo tiverem alcançados esta rotina, todos são liberados para seguirem com suas computações. Consulte a Figura 3.1.

Refer to caption
Figura 3.1: Barreira de sincronização.
Exemplo 3.3.1.

No Código 10, cada instância de processamento aguarda randomicamente até 3 segundos para alcançar a rotina de sincronização MPI_Barrier (na linha 15). Em seguida, elas são liberadas juntas. Estude o código.

Código 10: barrier.cpp
1// cronômetro
2time_t init = time (NULL);
3
4// semente do gerador randômico
5srand (init + world_rank);
6
7// max. of 3 segundos
8size_t espera = rand() % 3000000;
9
10usleep (espera);
11
12printf ("%d chegou na barreira: %ld s.\n",
13 world_rank, (time (NULL) - init));
14
15MPI_Barrier (MPI_COMM_WORLD);
16
17printf ("%d saiu da barreira: %ld s.\n",
18 world_rank, (time (NULL) - init));
0 chegou na barreira: 0 s.
3 chegou na barreira: 1 s.
1 chegou na barreira: 1 s.
2 chegou na barreira: 2 s.
2 saiu da barreira: 2 s.
0 saiu da barreira: 2 s.
3 saiu da barreira: 2 s.
1 saiu da barreira: 2 s.

3.3.2 Transmissão coletiva

A rotina de transmissão de dados MPI_Bcast permite o envio de dados de um processo para todos os processos (incluindo a si próprio). Consulte a Figura 3.2.

Refer to caption
Figura 3.2: Transmissão coletiva de dados.

Sua sintaxe é a seguinte:

1int MPI_Bcast(
2 void *buffer,
3 int count,
4 MPI_Datatype datatype,
5 int root,
6 MPI_Comm comm)

O primeiro argumento buffer aponta para o endereço da memória do dado a ser transmitido. O argumento count é a quantidade de dados sucessivos que serão transmitidos. O tipo de dado é informado no argumento datatype. Por fim, root é o identificador rank do processo que está transmitindo e comm é o comunicador.

Exemplo 3.3.2.

No seguinte Código 11, o processo 0 inicializa a variável de ponto flutuante x=π e, então, transmite ela para todos os demais processos. Por fim, cada processo imprime no terminal o valor alocado na sua variável x.

Código 11: bcast.cpp
1double x;
2if (world_rank == 0) {
3 x = M_PI;
4}
5
6MPI_Bcast(&x, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
7
8printf("Processo %d: x = %f\n", world_rank, x);
Processo 0: x = 3.141593
Processo 1: x = 3.141593
Processo 3: x = 3.141593
Processo 2: x = 3.141593

3.3.3 Distribuição coletiva de dados

A rotina MPI_Scatter permite que um processo faça a distribuição uniforme de pedaços sequenciais de um array de dados para todos os processos. Consulte a Figura 3.3.

Refer to caption
Figura 3.3: Distribuição coletiva de dados.

Sua sintaxe é a seguinte:

1int MPI_Scatter(
2 const void *sendbuf,
3 int sendcount,
4 MPI_Datatype sendtype,
5 void *recvbuf,
6 int recvcount,
7 MPI_Datatype recvtype,
8 int root,
9 MPI_Comm comm)

O primeiro argumento sendbuf aponta para o endereço de memória da array de dados a ser distribuída. O argumento sendcount é o tamanho do pedaço e sendtype é o tipo de dado a ser transmitido. Os argumentos recvbuf, recvcount e recvtype se referem ao ponteiro para o local de memória onde o dado recebido será alocado, o tamanho do pedaço a ser recebido e o tipo de dado, respectivamente. Por fim, o argumento root identifica o processo de origem da distribuição dos dados e comm é o comunicador.

Exemplo 3.3.3.

No Código scatter.cc abaixo, o processo 0 aloca o vetor

v=(1,2,,8), (3.5)

distribui pedaços sequenciais do vetor para cada processo no comunicador MPI_COMM_WORLD e, então, cada processo imprime o pedaço recebido.

Código 12: scatter.cpp
1int n = 8;
2Eigen::VectorXd v;
3Eigen::VectorXd chunck(n/world_size);
4
5if (world_rank == 0) {
6 v.resize(n);
7 for (int i = 0; i < n; i++)
8 v(i) = i + 1;
9}
10
11MPI_Scatter(v.data(), n/world_size, MPI_DOUBLE,
12 chunck.data(), n/world_size, MPI_DOUBLE, 0, MPI_COMM_WORLD);
13
14std::cout << "Processo " << world_rank
15 << " : " << chunck.transpose() << std::endl;
Processo 2 : 5 6
Processo 3 : 7 8
Processo 0 : 1 2
Processo 1 : 3 4
Observação 3.3.1.(MPI_Scatter)

A MPI_Scatter distribuí apenas pedaços de tamanhos iguais de um array. Para a distribuição de pedaços de tamanhos diferentes entre os processos, pode-se usar a rotina MPI_Scatterv.

3.3.4 Recebimento coletivo de dados distribuídos

A rotina MPI_Gather permite que um processo colete simultaneamente dados que estão distribuídos entre os demais processos. Consulte a Figura 3.4.

Refer to caption
Figura 3.4: Recebimento coletivo de dados distribuídos.

Sua sintaxe é a seguinte:

1int MPI_Gather(
2 const void *sendbuf,
3 int sendcount,
4 MPI_Datatype sendtype,
5 void *recvbuf,
6 int recvcount,
7 MPI_Datatype recvtype,
8 int root,
9 MPI_Comm comm)

Sua sintaxe é parecida com a da rotina MPI_Scatter (consulte a Subseção anterior). Veja lá! Aqui, root é o identificador rank do processo receptor.

Exemplo 3.3.4.

No Código 13, cada processo i aloca um vetor

my_v=(5i+1,5i+2,,5i+5), (3.6)

então, o processo 0 recebe estes vetores alocando-os em um único vetor

𝚟=(0,1,,5np1), (3.7)

onde np é o número de processos inicializados.

Código 13: gather.cpp
1int size = 5*world_size;
2int my_size = 5;
3Eigen::VectorXd my_v(5);
4for (int i = 0; i < 5; i++)
5 my_v(i) = 5*world_rank+i;
6
7std::cout << "Processo " << world_rank
8 << " : " << my_v.transpose() << std::endl;
9
10Eigen::VectorXd v;
11
12if (world_rank == 0)
13 v.resize(size);
14
15MPI_Gather(my_v.data(), 5, MPI_DOUBLE,
16 v.data(), 5, MPI_DOUBLE, 0, MPI_COMM_WORLD);
17
18if (world_rank == 0)
19{
20 std::cout << "Processo " << world_rank
21 << " : " << v.transpose() << std::endl;
22}
Processo 2 : 10 11 12 13 14
Processo 3 : 15 16 17 18 19
Processo 0 : 0 1 2 3 4
Processo 1 : 5 6 7 8 9
Processo 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Observação 3.3.2.(MPI_Gather)

Para recebimento de pedaços distribuídos e de tamanhos diferentes, pode-se usar a rotina MPI_Gatherv.

Observação 3.3.3.(MPI_Allgather)

A rotina MPI_Allgather nos permite juntar os pedaços de dados distribuídos e ter uma cópia completa em todos os processos. Sua sintaxe é a seguinte

1int MPI_Allgather(
2 const void *sendbuf,
3 int sendcount,
4 MPI_Datatype sendtype,
5 void *recvbuf,
6 int recvcount,
7 MPI_Datatype recvtype,
8 MPI_Comm comm)

Observemos que esta rotina não contém o argumento root, pois, neste caso, todos os processos são receptores.

3.3.5 Exercícios

E. 3.3.1.

Faça um código MPI para computar a média aritmética simples de n números randômicos em ponto flutuante.

E. 3.3.2.

Faça um código MPI para computar o produto interno de dois vetores de n elementos randômicos em ponto flutuante.

E. 3.3.3.

Faça um código MPI para computar a norma L2 de um vetor de n elementos randômicos em ponto flutuante.

E. 3.3.4.

Faça um código MPI para computar

erf(x)=2π0xet2𝑑t (3.8)

usando a regra composta do ponto médio.

E. 3.3.5.

Faça uma implementação MPI do método de Jacobi para computar a solução de um sistema Ax=b n×n. Inicialize A e b com números randômicos em ponto flutuante.


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.3 Comunicações coletivas

Rotinas MPI de comunicações coletivas são aquelas que envolvem múltiplas instâncias de processamento. Elas são utilizadas para a troca de dados entre processos, onde um ou mais processos enviam dados para todos os demais processos ou recebem dados de todos os demais processos.

3.3.1 Barreira de sincronização

Podemos forçar a sincronização de todos os processos em um determinado ponto do código utilizando a rotina de sincronização MPI_Barrier. Sua sintaxe é a seguinte

1int MPI_Barrier (MPI_Comm comm)

Quando um processo encontra esta rotina ele aguarda todos os demais processos. No momento em que todos os processo tiverem alcançados esta rotina, todos são liberados para seguirem com suas computações. Consulte a Figura 3.1.

Refer to caption
Figura 3.1: Barreira de sincronização.
Exemplo 3.3.1.

No Código 10, cada instância de processamento aguarda randomicamente até 3 segundos para alcançar a rotina de sincronização MPI_Barrier (na linha 15). Em seguida, elas são liberadas juntas. Estude o código.

Código 10: barrier.cpp
1// cronômetro
2time_t init = time (NULL);
3
4// semente do gerador randômico
5srand (init + world_rank);
6
7// max. of 3 segundos
8size_t espera = rand() % 3000000;
9
10usleep (espera);
11
12printf ("%d chegou na barreira: %ld s.\n",
13 world_rank, (time (NULL) - init));
14
15MPI_Barrier (MPI_COMM_WORLD);
16
17printf ("%d saiu da barreira: %ld s.\n",
18 world_rank, (time (NULL) - init));
0 chegou na barreira: 0 s.
3 chegou na barreira: 1 s.
1 chegou na barreira: 1 s.
2 chegou na barreira: 2 s.
2 saiu da barreira: 2 s.
0 saiu da barreira: 2 s.
3 saiu da barreira: 2 s.
1 saiu da barreira: 2 s.

3.3.2 Transmissão coletiva

A rotina de transmissão de dados MPI_Bcast permite o envio de dados de um processo para todos os processos (incluindo a si próprio). Consulte a Figura 3.2.

Refer to caption
Figura 3.2: Transmissão coletiva de dados.

Sua sintaxe é a seguinte:

1int MPI_Bcast(
2 void *buffer,
3 int count,
4 MPI_Datatype datatype,
5 int root,
6 MPI_Comm comm)

O primeiro argumento buffer aponta para o endereço da memória do dado a ser transmitido. O argumento count é a quantidade de dados sucessivos que serão transmitidos. O tipo de dado é informado no argumento datatype. Por fim, root é o identificador rank do processo que está transmitindo e comm é o comunicador.

Exemplo 3.3.2.

No seguinte Código 11, o processo 0 inicializa a variável de ponto flutuante x=π e, então, transmite ela para todos os demais processos. Por fim, cada processo imprime no terminal o valor alocado na sua variável x.

Código 11: bcast.cpp
1double x;
2if (world_rank == 0) {
3 x = M_PI;
4}
5
6MPI_Bcast(&x, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
7
8printf("Processo %d: x = %f\n", world_rank, x);
Processo 0: x = 3.141593
Processo 1: x = 3.141593
Processo 3: x = 3.141593
Processo 2: x = 3.141593

3.3.3 Distribuição coletiva de dados

A rotina MPI_Scatter permite que um processo faça a distribuição uniforme de pedaços sequenciais de um array de dados para todos os processos. Consulte a Figura 3.3.

Refer to caption
Figura 3.3: Distribuição coletiva de dados.

Sua sintaxe é a seguinte:

1int MPI_Scatter(
2 const void *sendbuf,
3 int sendcount,
4 MPI_Datatype sendtype,
5 void *recvbuf,
6 int recvcount,
7 MPI_Datatype recvtype,
8 int root,
9 MPI_Comm comm)

O primeiro argumento sendbuf aponta para o endereço de memória da array de dados a ser distribuída. O argumento sendcount é o tamanho do pedaço e sendtype é o tipo de dado a ser transmitido. Os argumentos recvbuf, recvcount e recvtype se referem ao ponteiro para o local de memória onde o dado recebido será alocado, o tamanho do pedaço a ser recebido e o tipo de dado, respectivamente. Por fim, o argumento root identifica o processo de origem da distribuição dos dados e comm é o comunicador.

Exemplo 3.3.3.

No Código scatter.cc abaixo, o processo 0 aloca o vetor

v=(1,2,,8), (3.5)

distribui pedaços sequenciais do vetor para cada processo no comunicador MPI_COMM_WORLD e, então, cada processo imprime o pedaço recebido.

Código 12: scatter.cpp
1int n = 8;
2Eigen::VectorXd v;
3Eigen::VectorXd chunck(n/world_size);
4
5if (world_rank == 0) {
6 v.resize(n);
7 for (int i = 0; i < n; i++)
8 v(i) = i + 1;
9}
10
11MPI_Scatter(v.data(), n/world_size, MPI_DOUBLE,
12 chunck.data(), n/world_size, MPI_DOUBLE, 0, MPI_COMM_WORLD);
13
14std::cout << "Processo " << world_rank
15 << " : " << chunck.transpose() << std::endl;
Processo 2 : 5 6
Processo 3 : 7 8
Processo 0 : 1 2
Processo 1 : 3 4
Observação 3.3.1.(MPI_Scatter)

A MPI_Scatter distribuí apenas pedaços de tamanhos iguais de um array. Para a distribuição de pedaços de tamanhos diferentes entre os processos, pode-se usar a rotina MPI_Scatterv.

3.3.4 Recebimento coletivo de dados distribuídos

A rotina MPI_Gather permite que um processo colete simultaneamente dados que estão distribuídos entre os demais processos. Consulte a Figura 3.4.

Refer to caption
Figura 3.4: Recebimento coletivo de dados distribuídos.

Sua sintaxe é a seguinte:

1int MPI_Gather(
2 const void *sendbuf,
3 int sendcount,
4 MPI_Datatype sendtype,
5 void *recvbuf,
6 int recvcount,
7 MPI_Datatype recvtype,
8 int root,
9 MPI_Comm comm)

Sua sintaxe é parecida com a da rotina MPI_Scatter (consulte a Subseção anterior). Veja lá! Aqui, root é o identificador rank do processo receptor.

Exemplo 3.3.4.

No Código 13, cada processo i aloca um vetor

my_v=(5i+1,5i+2,,5i+5), (3.6)

então, o processo 0 recebe estes vetores alocando-os em um único vetor

𝚟=(0,1,,5np1), (3.7)

onde np é o número de processos inicializados.

Código 13: gather.cpp
1int size = 5*world_size;
2int my_size = 5;
3Eigen::VectorXd my_v(5);
4for (int i = 0; i < 5; i++)
5 my_v(i) = 5*world_rank+i;
6
7std::cout << "Processo " << world_rank
8 << " : " << my_v.transpose() << std::endl;
9
10Eigen::VectorXd v;
11
12if (world_rank == 0)
13 v.resize(size);
14
15MPI_Gather(my_v.data(), 5, MPI_DOUBLE,
16 v.data(), 5, MPI_DOUBLE, 0, MPI_COMM_WORLD);
17
18if (world_rank == 0)
19{
20 std::cout << "Processo " << world_rank
21 << " : " << v.transpose() << std::endl;
22}
Processo 2 : 10 11 12 13 14
Processo 3 : 15 16 17 18 19
Processo 0 : 0 1 2 3 4
Processo 1 : 5 6 7 8 9
Processo 0 : 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Observação 3.3.2.(MPI_Gather)

Para recebimento de pedaços distribuídos e de tamanhos diferentes, pode-se usar a rotina MPI_Gatherv.

Observação 3.3.3.(MPI_Allgather)

A rotina MPI_Allgather nos permite juntar os pedaços de dados distribuídos e ter uma cópia completa em todos os processos. Sua sintaxe é a seguinte

1int MPI_Allgather(
2 const void *sendbuf,
3 int sendcount,
4 MPI_Datatype sendtype,
5 void *recvbuf,
6 int recvcount,
7 MPI_Datatype recvtype,
8 MPI_Comm comm)

Observemos que esta rotina não contém o argumento root, pois, neste caso, todos os processos são receptores.

3.3.5 Exercícios

E. 3.3.1.

Faça um código MPI para computar a média aritmética simples de n números randômicos em ponto flutuante.

E. 3.3.2.

Faça um código MPI para computar o produto interno de dois vetores de n elementos randômicos em ponto flutuante.

E. 3.3.3.

Faça um código MPI para computar a norma L2 de um vetor de n elementos randômicos em ponto flutuante.

E. 3.3.4.

Faça um código MPI para computar

erf(x)=2π0xet2𝑑t (3.8)

usando a regra composta do ponto médio.

E. 3.3.5.

Faça uma implementação MPI do método de Jacobi para computar a solução de um sistema Ax=b n×n. Inicialize A e b com números randômicos em ponto flutuante.


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
| | | |