• Nenhum resultado encontrado

Produto assíncrono de matriz por vetor em arquitetura anel.

Entrada: Número do processador; o número de processadores p, a i-ésima submatriz B = A(1:n, (i-1).(r+1): ir) de tamanho nxr (onde r = n / p) e o i-ésimo subvetor W= x((i-1).r+1:ir) de tamanho r.

Saída: Processador Pi calcula o vetor Y=A1x1 +A2x2 +.... +Aixi e passa o resultado à direita.

Quando o algoritmo termina, P1 terá o produto Ax.

inicio

compute produto(Z = B.W)

se i=1 então Y:=0 senão recebe(Y, esquerdo) Y:=Y+Z

envia(Y,direito)

se i=1 então recebe(Y, esquerdo) fim

Cada processador Pi começa computando Ai .

Xi e armazena o vetor resultante na variável local z. Depois do passo 2, o processador P1 inicializa o vetor Y como zero, onde cada um dos outros processadores suspende a execução de seus programas, aguardando o recebimento de dados dos seu vizinho esquerdo. O processador P1 atribui Y=A1.X1 e envia o resultado para seu vizinho da direita nos passos 3 e 4 (respectivamente). Então, o processador P2 recebe A1.X1 e conclui a execução de seu programa computando A1.X1 + A2.X2 no passo 3. Ele então envia o novo vetor para o processador vizinho à direita no passo 4. Quando a execução de todos os programas termina, o processador P1 terá o produto Y=A.X.

A computação efetuada por cada processador consiste de duas operações nos passos 1 e 3; portanto, o tempo de execução do algoritmo é O(n2/p). Por outro lado, o

processador P1 tem que esperar até que a soma parcial A1.X1+...+Ap.Xp tenha sido acumulada antes de executar o passo 5. Entretanto, o tempo total de comunicação é proporcional ao produto p.comm(n), onde comm(n) é o tempo gasto para se transmitir n

números entre processadores adjacentes. Esse valor pode ser aproximado por

comm(n) = σ +nτ, onde σ é o tempo de início de transmissão e τ é o intervalo de tempo

necessário para a mensagem ser transferida. A complexidade do algoritmo é a soma do tempo de execução e do tempo de comunicação. Esse tempo é minimizado, quando se tem p= n√α /(σ+nτ). #include <string.h> #include "mpi.h" #include <math.h> #include <stdio.h> #include <stdlib.h> #define DIME 21 #define MAX_BUF 1000000 MPI_Status Status; int my_id, size, posicao;

float *matriz, *vetor, *vetorr, *pvetor, *pvetor2; float *mt;

void calcula(int tamp, float *M, float *V, float *VR)

{ int v1, v2, v3, vf1; v3 = 0;

for (v1=0;v1<DIME;v1++) { for (v2=0;v2<tamp;v2++) { VR[v1]+= V[v2]*M[v3]; v3 ++; } } }

void carrega_matriz()

{ int cont1, cont2, tamanho, em, pm; tamanho = DIME * DIME;

matriz=malloc(tamanho*sizeof(float)); vetor=malloc(DIME*sizeof(float)); vetorr=malloc(DIME*sizeof(float)); pm = 0; em = 0;

for (cont1=0;cont1<DIME;cont1++) {em++; for (cont2=0;cont2<DIME;cont2++) {matriz[pm] = em; pm ++;} } for (cont1=0;cont1<DIME;cont1++) vetor[cont1] = 1;

for (cont1=0;cont1<DIME;cont1++) vetorr[cont1] = 0; }

void recebe_result()

{ int nd, ls;

pvetor2 =malloc(DIME*sizeof(float)); for (nd=1;nd<size;nd++)

{ MPI_Recv(&bpack,MAX_BUF,MPI_PACKED,MPI_ANY_SOURCE, MPI_ANY_TAG,MPI_COMM_WORLD,&Status); posicao = 0;

MPI_Unpack(&bpack,MAX_BUF,&posicao,pvetor2,DIME, MPI_FLOAT,MPI_COMM_WORLD); for (ls=0;ls<DIME;ls++) vetorr[ls] = vetorr[ls] + pvetor2[ls]; }

free(pvetor2); free(vetorr); }

void distribui_matriz()

{ int nll, sll, taml, nd; div_t partes; int vf1, vf2, posi, apo; partes = div(DIME,size); nll = partes.quot; sll = partes.rem; taml = (nll+sll); mt=malloc((taml*DIME)*sizeof(float)); pvetor=malloc((nll+sll)*sizeof(float)); for (nd=1;nd<size;nd++) { apo = 0; posi = (sll+(nll*nd));

for (vf1=0;vf1<DIME;vf1++){ for (vf2=0;vf2<nll;vf2++) { mt[apo]= matriz[posi]; apo++; posi++;} posi= posi+DIME-nll;} posi = nd * nll;

for (vf1=0;vf1<nll;vf1++) { pvetor[vf1] = vetor[posi]; posi++;} posicao = 0; MPI_Pack(&nll,1,MPI_INT,&bpack,MAX_BUF,&posicao,MPI_COMM_WORLD); MPI_Pack(mt,(DIME*nll),MPI_FLOAT,&bpack,MAX_BUF,&posicao,MPI_COMM_WORLD); MPI_Pack(pvetor,nll,MPI_FLOAT,&bpack,MAX_BUF,&posicao,MPI_COMM_WORLD); MPI_Send(&bpack,posicao,MPI_PACKED,nd,0,MPI_COMM_WORLD); } apo = 0; posi = 0; for (vf1=0;vf1<DIME;vf1++)

{ for (vf2=0;vf2<taml;vf2++) {mt[apo] = matriz[posi]; apo++; posi++;} posi = posi + DIME - taml; } for (vf1=0;vf1<taml;vf1++) pvetor[vf1] = vetor[vf1];

free(matriz); free(vetor); calcula(taml,mt,pvetor,vetorr); free(mt); free(pvetor); }

main(int argc, char **argv)

{ int nlp, aux;

MPI_Init(&argc,&argv);

MPI_Comm_size(MPI_COMM_WORLD,&size); MPI_Comm_rank(MPI_COMM_WORLD,&my_id);

if (my_id == 0) /* esta parte do codigo sera executado pelo processo zero */ { carrega_matriz(); distribui_matriz(); recebe_result(); }

else /* esta parte sera executada pelos demais processos */

{MPI_Recv(&bpack,MAX_BUF,MPI_PACKED,MPI_ANY_SOURCE, MPI_ANY_TAG,MPI_COMM_WORLD, &Status); posicao = 0; MPI_Unpack(&bpack,MAX_BUF,&posicao,&nlp,1,MPI_INT,MPI_COMM_WORLD); aux = nlp*DIME; mt=malloc(aux*sizeof(float)); vetor=malloc(nlp*sizeof(float)); vetorr=malloc(DIME*sizeof(float)) MPI_Unpack(&bpack,MAX_BUF,&posicao,mt,aux,MPI_FLOAT,MPI_COMM_WORLD); MPI_Unpack(&bpack,MAX_BUF,&posicao,vetor,nlp,MPI_FLOAT,MPI_COMM_WORLD); calcula(nlp,mt,vetor,vetorr); posicao = 0; MPI_Pack(vetorr,DIME,MPI_FLOAT,&bpack,MAX_BUF,&posicao,MPI_COMM_WORLD); MPI_Send(&bpack,posicao,MPI_PACKED,0,0,MPI_COMM_WORLD); free(vetor); free(mt); } MPI_Finalize( ); }

Exemplo 3.6.2 Multiplicação de Matrizes baseado em troca de mensagens.

Considere o produto da matriz M 8x8, por ela mesma (MxM), descrita na figura 3.17. O programa baseado em troca de mensagens, descrito na fig.3.16, realiza apenas o produto de uma matriz por um vetor. Ter-se-ia que executar o programa 8 vezes, uma para cada coluna, para se ter o produto completo. Considere que existam 4 processadores disponíveis (p=4), seus identificadores são 0, 1, 2, 3. O processador 0 possui o domínio completo. Ele divide-o entre 4 processadores, como mostra a fig.3.17.

P0 P1 P2 P3 1 2 3 4 5 6 7 8 P0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 P1 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 x 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 P2 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 P3 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 57 58 59 60 61 62 63 64

Figura 3.17 Exemplo de Multiplicação de Matrizes pelo programa da fig 3.16

Após a divisão, o processador 0 distribui os subdomínios entre os processadores, ficando com um subdomínio para processar. A distribuição é feita por mensagens de envio (send), como ilustrado na fig.3.18 (a).

P1

P2

P3

P0

(a)Distribuição de dados - send

P1

P2

P3

P0

(b)Recebimento dos dados - receive

Figura 3.18 Troca de Dados

Após o envio/recebimento cada processador calcula sua parte, como ilustrado na fig.3.19. E, então, são remetidos de volta ao processador 0 (fig. 3.18 (b)).

P0 P1 P2 P3 1 9 17 25 33 41 49 57 X X X X 1 2 19 3 4 151 5 6 411 7 8 799 9 10 99 11 12 487 13 14 1003 15 16 1647 17 18 179 19 20 823 21 22 1595 23 24 2495 25 26 = 259 27 28 = 1159 29 30 = 2187 31 32 = 3343 33 34 339 35 36 1495 37 38 2779 39 40 4191 41 42 419 43 44 1831 45 46 3371 47 48 5039 49 50 499 51 52 2167 53 54 3963 55 56 5887 57 58 579 59 60 2503 61 62 4555 63 64 6735

Figura 3.19 Cálculo local em cada processador

Por fim, o processador 0 efetua a soma dos resultados parciais, calculados em cada processador, gerando o vetor produto desejado. Isso é mostrado na fig.3.20.

19 + 151 + 411 + 799 = 1380 99 + 487 + 1003 + 1647 = 3236 179 + 823 + 1595 + 2495 = 5092 259 + 1159 + 2187 + 3343 = 6948 339 + 1495 + 2779 + 4191 = 8804 419 + 1831 + 3371 + 5039 = 10660 499 + 2167 + 3963 + 5887 = 12516 579 + 2503 + 4555 + 6735 = 14372

Figura 3.20 Cálculo vetor produto - soma dos valores parciais

3.6.3 Considerações sobre complexidade

O algoritmo trivial para multiplicação de matrizes, tendo a complexidade de tempo calculado em função da operação de multiplicação (e adição) é da ordem O (n3).

Existem, entretanto, algoritmos recursivos para multiplicação de matrizes são mais eficientes, como o algoritmo de Strassen [STR69], que possui uma complexidade de O (n log

27) = O (n2.81).

A complexidade de tempo do algoritmo de Strassen, quando executado em um computador com arquitetura com p processadores e com a memória global suficientemente rápida para satisfazer todos os requisitos desses p processadores, é Ο(n3/p). Para computadores que se utilizem de troca de mensagens, a complexidade de tempo desse algoritmo é afetada pelo tempo de comunicação (troca de dados), que possui uma complexidade de comunicação da ordem de Ο (n2 (1-(1/p))). Portanto, a complexidade de tempo nesse caso é de Ο (n2(1-(1/p))+n3/p).

A complexidade de tempo e de comunicação do Algoritmo 3.6.4, pode ser estimada, pela análise de cada instrução e assim, tem-se a complexidade total do algoritmo, como:

• o passo 3 é o único passo que requer O(n2/p) operações aritméticas; • os passos 1 e 2 transferem O(n2/p) dados (números) da memória

compartilhada para a memória local de cada processador;

• o passo 4 armazena n/p números de cada memória local na memória compartilhada.

Algoritmo 3.6.6 é executado em um tempo O(log n) usando um total de O(n3) processadores. Pelo princípio de escalonamento, o algoritmo pode ser simulado através de p processadores sendo executado em um tempo O(n3/p + log n).

Considere a adaptação desse algoritmo para o caso onde há n processadores disponíveis. Em particular, o tempo de execução correspondente deve ser O(n2), no exame da complexidade de comunicação de Algoritmo 3.6.6 relativo a um esquema particular de alocação de processadores. Um esquema direto consiste em alocar as operações incluídas em cada unidade de tempo aos processadores. Em particular, as n3 operações simultâneas do passo 1 podem ser alocadas igualmente entre os n processadores como segue. Para cada 1 i n, o processador Picomputa C'(i, j, l) = A(i, l)B(l, j), onde 1 j, l n; logo, Pi tem que ler a i-ésima linha de A e toda a matriz B da memória compartilhada. Um tráfico de O(n2) números é criado entre a memória compartilhada e cada uma das memórias locais dos processadores.

A h-ésima iteração do laço no passo 2 requer n3/2h operações simultâneas que podem ser alocadas como segue. A tarefa do processador Pi é atualizar os valores C'(i,

j, l), para todo o 1 j, l n; logo, Pi pode ler todos valores necessários C'(i, j, l) para todos os índices 1 j, l n, e pode então executar as operações requeridas nesse

conjunto de valores. Novamente, O(n2) entradas são trocadas entre a memória compartilhada e a memória local de cada processador.

Finalmente, o passo 3 pode ser implementado facilmente com O(n) comunicações, desde que o processador Pi tenha que armazenar a i-ésima linha da matriz produto na

memória compartilhada, para 1 i n. Então, têm-se um esquema de alocação de processadores que adapta o princípio do escalonamento com uma exigência de comunicação de O(n2).

O limite superior para o problema de multiplicação de matrizes conhecido é O (n2.374), atribuído a Pan ([PAN78]).

3.7 Conclusões

Complexidade de um algoritmo é o esforço (quantidade de trabalho) de um algoritmo. As principais medidas de complexidade são tempo e espaço, relacionadas à velocidade e quantidade de memória, respectivamente. A complexidade depende da particular entrada: os principais critérios são pior caso e caso médio.

A complexidade é determinada com base em operações fundamentais e no tamanho da entrada. Ambos devem ser apropriados ao algoritmo ou ao problema. A complexidade experimental costuma depender de detalhes de implementação, variando de máquina a máquina. Análise matemática fornece a complexidade intrínseca do algoritmo.

A complexidade exata fornece muita informação, sendo, muitas vezes, difíceis suas determinação e análise. A complexidade assintótica dá o desempenho para entradas de tamanho grande.

As principais notações de comportamento assintótico dão limites superior (Ο), inferior (Ω) e exato (Θ), a menos de constantes multiplicativas. Algoritmos polinomiais têm complexidade Ο (nk); os com complexidade

Ω(2n) são exponenciais.

Observou-se que a computação do multiplicação de matrizes é um processo particularmente propício para o processamento paralelo síncrono. Entretanto, a análise realizada dos algoritmos sincronizados de produto de matrizes, levou ao entendimento de quais características específicas de arquiteturas para estruturas paralelas influenciam na determinação do melhor algoritmo paralelo.

A determinação do melhor algoritmo tem seu lado teórico e seu lado prático. No lado teórico, considera-se apenas a análise do algoritmo em função do número de operações fundamentais e do número ótimo de processadores necessários para essa resolução. Infelizmente, para comprovação prática dessa escolha, encontram-se outras dificuldades, como: tempo de comunicação, sincronização e troca de dados; o tamanho do problema pode vir a determinar diferentes algoritmos como ótimos em uma mesma máquina paralela, como por exemplo em uma máquina vetorial, onde o tamanho da entrada determinará se o tempo de montagem do pipeline (startup) irá ou não afetar o tempo total de processamento.

Percebe-se com esse curso que muitas questões ainda devem ser pesquisadas, para que se tenham repostas práticas que auxiliem na formação de um conhecimento básico de programação paralela, na criação de uma cultura paralela. Essas questões dizem respeito a importância do tempo de gerência (overhead) e seu impacto no tempo de processamento e na complexidade de um problema; impacto das contingências de memória no desempenho de programas paralelos; uso de algoritmos síncronos e assíncronos em diferentes ambientes; definição de linguagens de descrição de algoritmos paralelos capazes de descrever a exploração do paralelismo, de facilitar o

cálculo da complexidade de tempo e espaço; determinação do número ótimo de processadores, enfim. Esse curso apenas iniciou a discutir a importância da teoria, da análise de complexidade de algoritmos paralelos para o desenvolvimento de programas paralelos eficientes.

3.8 Agradecimentos

Agradecemos ao CNPq e a FAPERGS pelo suporte econômico aos projetos de pesquisa dos nossos Grupos, que viabilizaram o desenvolvimento desse trabalho e de muitos outros. E, por fim, aos bolsistas de Iniciação Científica e de Mestrado que nos auxiliaram no desenvolvimento desse trabalho, especialmente a Karina Kohl Silveira e o Delcino Picinin Junior.

3.9 Bibliografia

[AKL89] AKL, SELIM.G. The design and analysis of parallel algorithms.

Englewood Cliffs: Prentice Hall, 1989. 401p.

[BAT68] BATCHER, K.E. Sorting networks and their applications. In: SPRING JOINT COMPUTER CONFERENCE. Proceedings... Atlantic City, 30 april. Reston: AFIPS Press, 1968. pp.307-314.

[BED 89] BERTSEKAS, DIMITRI.P; TSITSIKLIS, JOHN.N. Parallel and

distributed computations numerical methods. Englewood Cliff:

Prentice Hall, 1989. 715p.

[BER 74] BERNSTEIN, P.A.; TSICHRITZIS, D. C. Operating Systems. New York: Academic Press, 1974. 298p.

[BOV93] BOVET, D.; CRESCENZI, P. Introduction to the Theory of

Complexity. Prentice Hall, 1993.

[CNA2001] CNAPAD. Introdução ao MPI. São Paulo, 2001.

[COL86] COLE, R. Parallel Mergsort. In: IEEE Focs, 27. Proceedings... 1986. p.511-516.

[COO71] COOK, S.A.The Complexity of Theorem-proving procedures. Proc. 3rd

Ann. ACM Symp. on Theory of Computation, Association for Computing Machinery, Proceedings... New York: 1971. p.151-158. [DIV92] DIVERIO, T. A; CLAUDIO, D. M.; NAVAUX, P. O. A. Divisão e

Conquista uma técnica para paralelização de algoritmos. Porto

Alegre: CPGCC da UFRGS, 1992. RP-177. 132p

[FLY 72] FLYNN, M. J. Some organizations and their effectiveness. IEEE

[FOS95] FOSTER, IAN T. Designing and Building Parallel Programs concepts

and tools for parallel software engineering. Reading: Addison-Wesley, 1995. 379p.

[GAR79] GAREY, M. R.; JOHNSON, D. S. Computers and Intractability: a

guide to the theory of NP-completeness. San Francisco: W. H.

Freeman, 1979.

[HIR78] HIRSCHBERG, D.S. Fast Parallel Sorting Algorithm. Communications

ACM. v.22, n.8, 1978.

[HWA 84] HWANG, KAI; BRIGGS FAYE ALAYE. Computer Architecture and

Parallel Processing. New York: McGraw Hill, 1984. 846p.

[JAJ92] JAJA, JOSEPH. An introduction to parallel algorithms. Reading:

Addison-Wesley, 1992. 566p.

[KRO86] KRÖNSJO,L. Computational complexity of sequential and parallel algorithms. Chichester: John Wiley, 1986.

[KUM94] KUMAR,V; GRAMA, A.; GUPTA, A.; KARYPIS, G. Introduction to

Parallel Computing - design and analysis of algorithms. Redwood

City: Benjamin/Cummings Publishing, 1994. 597p.

[MAN 89] MANBER U. Introduction to Algorithms: A Creative Approach Addison-Wesley, 1989.

[MUP75] MULLER, D.E.; PREPARATA, F.P. Bounds to complexities of networks for sorting and for switching. Journal of the ACM. v.22, n.2, p.195-201, 1975.

[PAC97] PACHECO, PETER S. Parallel Programming with MPI. San

Francisco: Morgan Kaufman, 1997. 418p.

[PAN78] PAN, V. Y. Strassen's algorithm is not optimal. In: ANNUAL SYMPOSIUM ON FOUNDATION OF COMPUTER SCIENSE, 19.

Proceedings... 1978.

[PIP79] PIPPNGER, NICHOLAS. On simultaneous resource bounds (preliminary version) In: IEEE Symposium on Foundations of Computer Science. Proceedings... Los Angeles: IEEE, 1979. p.307-311.

[PRE78] PREPARATA, F.P. New Parallel Sorting Schemes. IEEE

Transactions on Computers. v.C-22, n.7. 1978.

[QUI87] QUINN, MICHAEL JAY. Designing efficient algorithms for parallel computers. New York: McGraw-Hill, 1987. 288p.

[STO78] STONE, H. S. Sorting on Star. IEEE Transactions on Software

[STR69] STRASSEN, V. Gaussian elimination is not optimal. Numerische

Matematik. v.13. Berlin: Spring Verlag, 1969. pp.354-356.

[TAN76] TANENBAUM, ANDREW S. Structured Computer Organization. Englewood Cliffs, Prentice-Hall, 1976. 443p.

[TAN95] TANENBAUM, ANDREW S. Distributed Operating Systems. Upper Saddle River: Prentice-Hall, 1995. 614p.

[TER 90] TERADA,R. Introdução à complexidade de algoritmos paralelos. In: ESCOLA DE COMPUTAÇÃO, VII,1990, São Paulo, Curso. São Paulo, IME-USP, 1990. 104p.

[TOS2001] TOSCANI, L. V.; VELOSO, P.A.S. Complexidade de Algoritmos -

análise, projeto e métodos. Porto Alegre: Sagra-Luzzatto/Instituto de

Informática da UFRGS, 2001. 202p. {Série livros didáticos, número 13}. [TRA73] TRAUB, J. F. Complexity of sequential and parallel numerical

algorithms. New York: Academic Press, 1973. 300p.

[WIL99] WILKINSON, B.; ALLEN, M. Parallel Programming Techniques and

Applications using networked workstations and parallel computers.

Upper Saddle River: Prentice Hall, 1999. 431p.

[ZOM96] ZOMAYA, ALBERT Y. (Ed.). Parallel and Distributed Computing

Documentos relacionados