Para fazer paralelização nessa atividade, será usada a biblioteca OpenMP que utliza #pragma dos compiladores C para gerar código paralelo utilizando threads. Serão utilizadas 3 opções de pragmas. A primeira opção é o #pragma omp parallel, que paraleliza um bloco de código. Veja o exemplo abaixo:
#include <stdio.h>
main()
{
#pragma omp parallel
{
puts("Hello World");
}
}
Esse código pode ser compilado corretamente em qualquer compilador C mas, quando for compilado com um compilador com suporte a OpenMP, o código gerado utlizará threads para executar em paralelo. O gcc não supota OpenMP, nesse exercício o compilador C da Intel (icc) terá que ser utlizado. Para utilizar o icc, inclua no PATH o diretório /home/staff/rodolfo/intel/cc/9.0/bin e declare a variável de ambiente LD_LIBRARY_PATH com valor /home/staff/rodolfo/intel/cc/9.0/lib. Grave o código acima no arquivo hello.c e compile-o duas vezes, sem e com OpenMP:
icc hello.c -o hello
icc hello.c -o hello_openmp -openmp
Ao executar os dois programas, você verá que a versão com OpenMP
imprime uma mensagem para cada processador (real ou virtual) existente
no computador. O número de threads
que será
gerado depende do número de processadores disponíveis, mas também pode
ser especificado através da variável de ambiente OMP_NUM_THREADS. Faça um teste com
algumas opções para essa variável e veja as diferentes respostas. Só
que, em geral, não se quer replicar apenas o código, o ideal é dividir
a carga de trabalho entre as várias threads
geradas. A forma simples de fazer isso é paralelizar um laço for com #pragma omp for, colocado
imediatamente antes do for (há um exemplo logo abaixo na atividade). O
último pragma é #pragma omp
critical(variavel), que define como região crítica todo acesso à
variável especificada. Também há um exemplo nessa atividade dessa
construção.
As atividades devem ser realizadas
individualmente na sua totalidade. Usando o programa abaixo,
compile-o e execute em um
computador das salas 302 ou
303, usando o compilador da Intel.
#include <stdio.h>
#define MAXIMO 500
#define REPETICOES 10000
float A[MAXIMO][MAXIMO], B[MAXIMO][MAXIMO], C[MAXIMO][MAXIMO];
main()
{
int i, j, k;
for (i = 0; i < MAXIMO; i++)
for (j = 0; j < MAXIMO; j++) {
A[i][j] = i + j;
B[i][j] = i - j;
C[i][j] = 0;
}
#pragma omp parallel
{
#pragma omp for
for (k = 0; k < REPETICOES; k++)
for (i = 0; i < MAXIMO; i ++)
for (j = 0; j < MAXIMO; j++) {
A[i][j] += 1;
C[i][j] += A[i][j] + B[i][j];
}
}
}
Compile uma vez com cada um dos conjuntos de opções de compilação:
A opção -xN gera código exclusivo para Pentium 4, a opção -openmp utiliza os pragmas do OpenMP para gera código paralelo e a opção -parallel tenta paralelizar automaticamente o código (sem usar os pragmas do OpenMP). Execute os programas gerados, anote o tempo e comente no relatório, indicando quais foram as influências na execução em cada caso.
#include <stdio.h>
#include <math.h>
#define LIMITE 5000000
int primo(int numero)
{
int raiz, fator;
raiz = (int) sqrt((double) numero);
for(fator = 2; fator <= raiz; fator++)
if (numero % fator == 0)
return 0;
return 1;
}
int main()
{
int quantidade = 0, numero;
#pragma omp parallel for schedule(static, 8)
for(numero = 2; numero < LIMITE; numero ++) {
int p = primo(numero);
#pragma omp critical(quantidade)
quantidade += p;
}
printf("Total de numeros primos ate %d: %d\n", LIMITE, quantidade);
}
Meça o desempenho compilando com e sem a opção -openmp. Faça uma variação do código removendo a parte schedule(static, 8) e compare o desempenho das 3 execuções. O que schedule(static, 8) faz? Consulte o manual do OpenMP.
Dica geral: Se achar que os
tempos execução estiverem muito próximos, varie as constantes de
configuração para alterar os tempos de execução. Indique isso no seu
relatório e justifique suas escolhas.