[Desafio do mês] Programação paralela com o OpenMP

- por Sergio Prado

Categorias: Desafios Tags: , ,

Que tal aprender um pouco sobre programação paralela?

O desafio deste mês é sobre um framework de programação paralela chamado OpenMP.

Mas o que é programação paralela? E como funciona o OpenMP?

PROGRAMAÇÃO PARALELA

Ao invés de aumentar a frequência da CPU, os sistemas computacionais tem evoluído no sentido de utilizar mais de um processador no mesmo sistema para melhorar sua performance. Hoje por exemplo é comum os SoCs (System-on-Chip) utilizados em sistemas embarcados possuirem múltiplos núcleos (cores) de CPU.

E não adianta nada ter oito núcleos de CPU se a sua aplicação tem uma única linha de execução. Torna-se então necessário utilizar eficientemente os múltiplos núcleos de processamento existentes para melhorar a eficiência e a performance do sistema.

Existem diversas formas de paralelizar a execução de uma aplicação.

Uma delas é dividindo a aplicação em múltiplos processos. Neste caso, é necessário utilizar um mecanismo eficiente de comunicação entre os processos (memória compartilhada, mensagens, sockets, etc.) de forma que não tenhamos sobrecarga na comunicação entre os processos, e consequentemente prejudicando o paralelismo da aplicação.

Outra forma de paralelizar o processamento é dividindo a aplicação em múltiplas threads. Neste caso, é muito mais fácil implementar a comunicação entre as threads já que elas compartilham o espaço de endereçamento de memória. Um seja, uma variável global pode ser acessada diretamente pelas múltiplas threads da aplicação. Aqui o desafio está em implementar eficientemente e corretamente o acesso aos dados compartilhados entre as threads, por exemplo protegendo as seções críticas de acesso concorrente.

Agora, é claro que o desenvolvedor tem todo o trabalho de decidir o que e como paralelizar, implementar corretamente as threads e gerenciar a comunicação e a concorrência entre elas.

E se pudéssemos indicar ao compilador quais trechos do código podem ser paralelizados, e deixássemos que ele fizesse isso por nós? É exatamente esse o papel do OpenMP!

OPENMP

O OpenMP (Open Multi Processing) é um framework genérico de programação paralela suportado por diversos compiladores, incluindo o GCC.

Na prática, é uma API de programação paralela para escrever aplicações multithread, com suporte às linguagens C, C++ e Fortran. Através de diretivas de compilação e algumas rotinas de biblioteca, o desenvolvedor indica ao compilador o quê e como paralelizar!

Por exemplo, veja abaixo um programa de multiplicação de matrizes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>
 
#define ORDER 1024
#define AVAL 3.0
#define BVAL 5.0
#define TOL  0.001
 
int main(int argc, char **argv)
{
    int Ndim, Pdim, Mdim;   /* A[N][P], B[P][M], C[N][M] */
    double *A, *B, *C, tmp;
    int i, j, k;
 
    Ndim = Pdim = Mdim = ORDER;
 
    A = (double *) malloc(Ndim * Pdim * sizeof(double));
    B = (double *) malloc(Pdim * Mdim * sizeof(double));
    C = (double *) malloc(Ndim * Mdim * sizeof(double));
 
    /* initialize A[N][P] = AVAL */
    for (i = 0; i < Ndim; i++)
        for (j = 0; j < Pdim; j++)
            *(A+(i*Ndim+j)) = AVAL;
 
    /* initialize B[P][M] = BVAL */
    for (i = 0; i < Pdim; i++)
        for (j = 0; j < Mdim; j++)
            *(B+(i*Pdim+j)) = BVAL;
 
    /* initialize C[N][M] = 0.0 */
    for (i = 0; i < Ndim; i++)
        for (j = 0; j < Mdim; j++)
            *(C+(i*Ndim+j)) = 0.0;
 
    /* multiply the matrices */
    for (i = 0; i < Ndim; i++){
        for (j = 0; j < Mdim; j++){
            tmp = 0.0;
            for(k = 0; k < Pdim; k++){
                /* C(i,j) = sum(A(i,k) * B(k,j)) */
                tmp += (*(A+(i*Ndim+k))) * (*(B+(k*Pdim+j)));
            }
            *(C+(i*Ndim+j)) = tmp;
        }
    }
 
    printf("Done!\n");
    return 0;
}

Este programa leva em torno de 14 segundos para rodar na minha máquina (Intel Core i7 com 8 núcleos de CPU):

$ gcc matrix.c -o matrix
$ time ./matrix
Done!
 
real 0m14.296s
user 0m14.270s
sys 0m0.012s

Colocando uma única diretiva de compilação do OpenMP (linha 36) nós paralelizamos a execução do loop que faz o cálculo das matrizes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <stdio.h>
#include <stdlib.h>
#define ORDER 1024
#define AVAL 3.0
#define BVAL 5.0
#define TOL  0.001
 
int main(int argc, char **argv)
{
    int Ndim, Pdim, Mdim;   /* A[N][P], B[P][M], C[N][M] */
    double *A, *B, *C, tmp;
    int i, j, k;
 
    Ndim = Pdim = Mdim = ORDER;
 
    A = (double *) malloc(Ndim * Pdim * sizeof(double));
    B = (double *) malloc(Pdim * Mdim * sizeof(double));
    C = (double *) malloc(Ndim * Mdim * sizeof(double));
 
    /* initialize A[N][P] = AVAL */
    for (i = 0; i < Ndim; i++)
        for (j = 0; j < Pdim; j++)
            *(A+(i*Ndim+j)) = AVAL;
 
    /* initialize B[P][M] = BVAL */
    for (i = 0; i < Pdim; i++)
        for (j = 0; j < Mdim; j++)
            *(B+(i*Pdim+j)) = BVAL;
 
    /* initialize C[N][M] = 0.0 */
    for (i = 0; i < Ndim; i++)
        for (j = 0; j < Mdim; j++)
            *(C+(i*Ndim+j)) = 0.0;
 
    /* multiply the matrices */
    #pragma omp parallel for private(tmp, i, j, k) shared(Ndim, Mdim, Pdim)
    for (i = 0; i < Ndim; i++){
        for (j = 0; j < Mdim; j++){
            tmp = 0.0;
            for(k = 0;k < Pdim; k++){
                /* C(i,j) = sum(A(i,k) * B(k,j)) */
                tmp += (*(A+(i*Ndim+k))) * (*(B+(k*Pdim+j)));
            }
            *(C+(i*Ndim+j)) = tmp;
        }
    }
 
    printf("Done!\n");
    return 0;
}

E o tempo de execução da aplicação cai para 2,1 segundos! Fantástico!

$ gcc matrixmp.c -o matrixmp -fopenmp
$ time ./matrixmp
Done!
 
real 0m2.168s
user 0m16.448s
sys 0m0.012s

AGORA O DESAFIO

O código a seguir utiliza a aproximação do matemático francês François Viète para calcular o número Pi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
int main(int argc, char **argv) 
{
	unsigned long int n;
	double r, pi = 1;
	int i, j;
 
        if (argc != 2) {
		printf("Usage: <calc_pi> <number of iterations>\n");
		exit (1);
	}
 
	n = strtol(argv[1], NULL, 10);
	if (!n) {
		printf("ERROR: Invalid number of iterations!\n");
		exit (2);
	}
 
	for (i = 0; i < n; i++) {
 
		r = 2;
		for (j = i; j ; j--)
			r = 2 + sqrt(r);
		r = sqrt(r);
 
		pi *= (r / 2);
	}  
 
	pi = 2 / pi;
 
	printf("Aproximated value of PI = %1.16f\n", pi);
 
	return 0;
}

Executando a aplicação com 8.000 iterações em uma Raspberry Pi 3, o tempo total de processamento é de aproximadamente 10,64 segundos:

# time pi 8000
Aproximated value of PI = 3.1415926535897940
real 0m 10.64s
user 0m 10.64s
sys 0m 0.00s

Seu objetivo é utilizar o OpenMP para paralelizar a execução da aplicação e diminuir o tempo total de processamento. Existem algoritmos mais eficientes que o utilizado neste exercício, mas neste desafio não será permitido alterar o algoritmo em si. A idéia é realmente utilizar apenas as rotinas e diretivas de compilação do OpenMP para paralelizar o processamento da aplicação.

Os testes serão feitos em uma Raspberry Pi 3 com um sistema customizado criado pelo Buildroot, e a aplicação será executada com 8000 iterações, conforme exemplo acima.

Atualização 1: Se você não tiver uma Raspberry Pi 3 não tem problema. Você pode desenvolver e testar no seu PC mesmo, desde que ele seja multicore e de que a aplicação fique portável para que depois eu possa compilar e testar na Raspberry Pi 3.

Todas as respostas que, de alguma forma, paralelizarem o código com o OpenMP levarão os 5 pontos. Aqueles que conseguirem paralelizar e levar o menor tempo de execução levarão os 10 pontos e o concorrerão ao prêmio, que neste desafio será uma Beaglebone White!

Envie sua solução do desafio para o e-mail contato@sergioprado.org. Aceitarei submissões até o dia 24/02 e o resultado será divulgado até o dia 03/03.

Qualquer problema ou dúvida é só escrever nos comentários desta publicação.

E aí, vai topar este desafio? Instancie uma thread e comece a estudar! :)

Um abraço,

Sergio Prado

  • Jose Tadeu Aldrigue

    Boa tarde Professor. Posso testar o algoritmo em uma BBB, pois não tenho uma RPi no momento…

    • George Tavares

      Eu, nao tenho nem um , nem outro. Dureza…
      Vou ter que dar um setup de emulador, qemu da vida

      • Olá George!

        Você pode desenvolver e testar no PC mesmo. Veja o comentário acima.

    • Olá Jose Tadeu!

      Sim, pode testar em qualquer plataforma. O único problema de testar na BBB é o fato dela ter apenas um núcleo de CPU, e portanto qualquer otimização que fizer para paralelizar a execução não trará muito resultado.

      Se não tiver nenhuma plataforma embarcada multicore como a RPi 3 que tem 4 núcleos de CPU, você pode desenvolver e testar no seu PC mesmo, desde que ele seja multicore e de que o código fique portável para que eu possa compilar e testar na RPi 3.

  • Bem legal este desafio Sergio, vale o uso de qualquer recurso do OpenMP(funcões, macros, diretivas e tudo mais)?

    • Olá Cleiton!

      Sim, use e abuse do OpenMP! Só não vale mesmo alterar o algoritmo.

      • George Tavares

        Ola Sergio. Entendo que nao devemos mexer no algoritmo, mas podemos alterar um pouco o modo que o algoritmo trabalha? Por exemplo, ao inves de usar determinada variavel no seu codigo, Criar outras variaveis , com outros escopos e tal, mas mantendo a mesma logica?

        • Não! :-)

          Do post: “A idéia é realmente utilizar apenas as rotinas e diretivas de compilação do OpenMP para paralelizar o processamento da aplicação.”

          É claro que, como eu sou o autor do código, fique à vontade para me enviar sugestões de melhorias. Mas estas sugestão não valerão para a avaliação do desafio e não deverão estar implementadas na solução final para avaliação.

          Have fun!

  • Luís Antônio Martins dos Reis

    Olá Sérgio tudo bem? Estou fazendo minha versão utilizando o OpenMP, Acerca do segundo loop presente no código, as diretivas do OpenMP aceitam apenas a forma Canônica do for (expressão inicial; teste; incremento) sendo assim esta etapa não seria passível de paralelização, como proceder? É possível alter o teste j; para j>0? Obrigado.

    • Olá Luis!

      Infelizmente não posso dar dicas sobre o OpenMP até o final do desafio, mas posso te dizer que não é permitido nenhum tipo de alteração no código que deve ser otimizado. Apenas é permitido o uso de diretivas do OpenMP. Um abraço!

Navegue
Creative Commons Este trabalho de Sergio Prado é licenciado pelo
Creative Commons BY-NC-SA 3.0.