Paralelização do Método de Jacobi em Memória Compartilhada 1 Claudio Schepke cschepke@inf.ufrgs.br 1 PPGC - Instituto de Informática - UFRGS Introdução ao Processamento Paralelo e Distribuído Professor Philippe Navaux Resumo. A resolução de sistemas lineares através de métodos numéricos iterativos é uma das possibilidades adotadas para a busca da solução. No entanto, para sistemas com uma grande quantia de incógnitas, mesmo com a utilização de computadores com grande capacidade de processamento, as execuções podem ser demoradas. Uma solução para este problema está na utilização de arquiteturas paralelas. Neste contexto, este trabalho descreve uma implementação paralela do método de Jacobi para uma arquitetura com memória compartilhada, buscando aumentar a eficiḙncia do método através do uso de diferentes processos. 1. Introdução Diversos problemas físicos modelados matematicamente são discretizados e resolvidos computacionalmente [Lucquin and Pironneau, 1998]. Este é o caso das equações diferenciais parciais (EDPs), que podem ser representadas por sistemas de equações lineares [Saad and van der Vorst, 2]. Uma das formas que se demonstra adequada para a resolução desse tipo de sistemas é a utilização de métodos numéricos iterativos. Métodos numéricos iterativos buscam encontrar a solução de um sistema Ax = b, onde A é uma matriz de coeficientes, x é um vetor de incógnitas a ser determinado e b é o vetor de termos independentes. Através de sucessivas aproximações iterativas os métodos conseguem atingir uma solução aproximada da exata. No entanto, este processo pode ser bastante demorado, dependendo do tamanho do sistema a ser resolvido. Uma solução para melhorar o desempenho de métodos numéricos iterativos é a utilização de implementações paralelas, o que possibilita encontrar a solução de forma mais rápida, dividindo a carga de trabalho entre um número maior de processos [Keyes, 2]. Neste contexto, o presente trabalho apresenta uma implementação paralela do método de Jacobi para uma arquitetura com memória compartilhada [Dongarra et al., 1998, Dongarra et al., 1991]. A próxima seção apresenta a definição e descrição do método. Na seqüência são descritas as características da arquitetura escolhida. A quarta seção descreve a implementação paralela realizada para o método, bem como a experimentação feita através dum estudo de caso. Na quinta seção são apresentados os resultados obtidos e uma breve discussão dos mesmos. Por fim são apresentadas as conclusões obtidas com a realização do trabalho. 2. Método de Jacobi O método de Jacobi é um dos métodos numéricos mais simples e fáceis de paralelizar [Barrett et al., 1994, Dongarra et al., 1998]. Ele é obtido através da resolução de cada uma das equações do sistema linear Ax = b.
Para uma determinada equação i representada por n i= a i,j x j = b i pode-se obter o valor de x i através de x i = (b i j i a i,j x j )/a i,i De maneira iterativa x i é calculado em x (k) i = (b i j i a i,j x (k 1) j )/a i,i Desta forma, a resolução do sistema pode ser descrita através do algoritmo abaixo. JacobiMethod(A, x, b) initialize x () for k = 1 to N for i = 1 to n x i = for j = 1 to n x i = x i + a i,j xj k 1 x i = (b i x i )/a i,i x () = x check convergence Apesar do algoritmo apresentar a atualização de x i em uma determinada ordem, é possível realizar as operações de maneira desordenada, o que faciliza a implementação paralela do mesmo. 3. Arquiteturas com memória compartilhada Uma arquitetura com memória compartilhada é composta por um conjunto de processadores que compartilham um mesmo espaço de memória [Dongarra et al., 1991]. Esta é a maneira mais simples para que a comunicação entre dois processos seja realizada, uma vez que a zona de memória compartilhada é utilizada como se a mesma fosse um espaço de endereçamento que pertencesse a cada um dos programas. Uma das questões relacionadas a esse tipo de arquitetura é o mecanismo de acesso a memória, visto que cada um dos processadores vai carregar os dados em sua cache local. Assim são necessários técnicas que realizem a coerência de cache e a atualização da memória. Do ponto de vista da programação, existem diversos recursos para facilitar a programação concorrente. Algumas linguagens incorporam o conceito de threads [Andrews, 21], que permitem a instanciação de vários fluxos de execução Já em outras linguagens esse recurso é possível através do uso de bibliotecas especificamente desenvolvidas.
4. Implementação paralela do método e validação A paralelização do método neste trabalho ocorre através da divisão das operações do laço responsável pela obtenção das soluções parciais do vetor x. Assim, as operações do laço mais interno do método são distribuídas em N processos. A Figura 1 ilustra essa divisão do sistema em 3 partes. Cabe a cada um dos processos realizar as operações de uma dessas partes. Figura 1: Divisão do sistema de equações A implementação do algoritmos foi feita na linguagem Java [Gosling et al., 2, Boisvert et al., 21], utilizando a versão JDK 1.5. Para a criação dos processos foi utilizada a classe Threads, enquanto que a sincronização dos processos ocorre através do uso da interface Lock [Hyde, 1999]. As medições de tempo são feitas apenas durante o período de execução das threads, através da diferença de tempo obtida com a invocação do método System.currentTimeMillis(), antes e depois da execução das mesmas. Como estudo de caso foi utilizado a discretização de uma equação linear de Laplace. A precisão das soluções como garantia de convergência foi de 1 6 para a diferença entre a norma do vetor solução anterior e corrente da iteração. Os resultados obtidos representam a média de 1 execuções, que foram executadas em máquinas duais com 1GB de memória e 512 KB de cache L2. 5. Resultados e avaliação dos resultados As Figuras 2, 3, 4, 5 apresentam os resultados obtidos para as execuções com sistemas de 1, 3, 5 e 1 incógnitas, respectivamente. Para que o método encontrasse uma solução, segundo a tolerância definida, foram necessárias 12362, 81119, 1863 e 535247 iterações, respectivamente, para cada um dos casos anteriores. Nos gráficos, o número de processos varia de 1 a 8 para cada caso, sendo que o tempo de execução é medido em segundos. Os três primeiros casos foram executados numa máquina Pentium III, enquanto que para o último caso foi utilizado um computador AthlonMP24 de 2GHz. Através da análise dos gráficos, percebe-se que o tempo de execução com dois processos apresenta um ganho de desempenho muito bom em relação ao tempo seqüencial, especialmente a medida que o tamanho do sistema vai aumentando. De uma forma especial, o gráfico de tempo para o sistema de ordem 3 apresentou um ganho superlinear, o que pode ser explicado devido a um pior uso da cache no caso seqüencial. Nos gráficos, também é possível visualizar um tempo de execução maior com um número de processos ímpares em relação ao número de processos pares seguintes. Isto se deve a utilização de máquinas biprocessadas. Como geralmente são executados dois processos concorrentemente, no caso de processos ímpares, um dos processadores vai ficar ocioso enquanto o último processo for executado. Já em relação ao tempo utilizando o número de processo par seguinte, a carga total de trabalho será dividida por mais um processo, que poderá ser executado concorrentemente com outro processo. Um terceiro ponto observado é de que, quando não ocorre um bom aproveitamento do tamanho da cache, os tempos de execução começam a aumentar gradativamente na medida em que é utilizado um número maior de processadores. Isto ocorre devido a
6 "1" 5 4 3 2 1 1 2 3 4 5 6 7 8 Figura 2: Matriz de ordem 1-12362 iterações 3 "3" 25 2 15 1 5 1 2 3 4 5 6 7 8 Figura 3: Matriz de ordem 3-81119 iterações sobrecarga existente com a criação de novas threads. Por outro lado, este custo é relativamente mais baixo nos casos em que são utilizados sistemas com um grande número de incógnitas. Desta forma, parece natural a obtenção dum bom desempenho em sistemas multiprocessados que possuam um conjunto maior de processadores. 6. Conclusão O uso de sistemas multiprocessados é uma das formas existentes para explorar o paralelismo e, desta forma, aumentar o desempenho de um algoritmo. No caso de métodos
2 "5" 15 1 5 1 2 3 4 5 6 7 8 Figura 4: Matriz de ordem 5-1863 iterações 8 "1" 7 6 5 4 3 2 1 1 2 3 4 5 6 7 Figura 5: Matriz de ordem 1-535247 iterações numéricos, como o de Jacobi, esse tipo de arquitetura se apresenta como a melhor alternativa para a implementação de algoritmos eficientes [Dongarra et al., 1998]. Os resultados obtidos neste trabalho mostram que a eficiência do código é melhor a medida em que a carga de trabalho nos processadores é alta, o que atenua o tempo gasto na sincronização. Embora os testes tenham sido feitos em sistemas biprocessados é possível notar que o sobrecusto na sincronização é bastante pequeno, o que pode refletir num ganho de desempenho próximo do ideal com a utilização de mais processadores em sistemas maiores.
Como trabalhos futuros, é possível a utilização de sistemas que possuam mais processadores para confirmar a tendência de melhoria observada. Também é possível realizar a comparação de desempenho com outras implementações, como em multicomputadores, a fim de relacionar a eficiência entre as execuções. Referências Andrews, G. R. (21). Foundations of Multithreaded, Parallel, and Distributed Programming. Addison-Wesley, USA. Barrett, R., Berry, M., Chan, T. F., Demmell, J., Donato, J. M., Dongarra, J., Eijkhout, V., Pozo, R., Romine, C., and der Vorst, H. V. (1994). Templates for the Solution of Linear Systems: Building Blocks for Iterative Methods. SIAM, Philadelphia, PA. Boisvert, R. F., Moreira, J., Philippsen, M., and Pozo, R. (21). Java and numerical computing. IEEE Computing in Science and Engineering, 3(2):18 24. Dongarra, J. J., Duff, I. S., Sorensen, D. C., and van der Vorst, H. A. (1998). Numerical Linear Algebra for High-Performance Computers. SIAM, Philadelphia, PA. Dongarra, J. J., Duff, I. S., Sorensen, D. C., and Vorst, H. V. D. (1991). Solving Linear Systems on Vector and Shared Memory Computers. Society for Industrial & Applied Mathematics, Philadelphia, PA. Gosling, J., Joy, B., Steele, G., and Bracha, G. (2). The Java Language Specification Second Edition. Addison-Wesley, Boston, Mass. Hyde, P. (1999). Java Thread Programming. Sams Publishing, Indianapolis, Indiana, USA. Keyes, D. E. (2). Four horizons for enhancing the performance of parallel simulations based on partial differential equations. Lucquin, B. and Pironneau, O. (1998). Introduction to Scientific Computing. John Wiley & Sons, New York, USA. Saad, Y. and van der Vorst, H. A. (2). Iterative solution of linear systems in the 2th century. J. Comput. Appl. Math., 123(1-2):1 33.