Universidade de São Paulo Instituto de Ciências Matemáticas e de Computação Cálculo Aproximado do número PI utilizando Programação Paralela Grupo 17 Raphael Ferras Renan Pagaiane Yule Vaz SSC-0143 Programação Concorrente São Carlos Março de 2012
1 - Introdução... 3 1.1 - Gauss-Legendre... 3 1.2 - Borwein... 3 1.3 - Monte Carlo... 3 2 - Desenvolvimento... 4 2.1 - Gauss-Legendre... 4 2.1.1 - Versão Seqüencial... 4 2.1.2 - Versão Paralela... 4 2.2 - Borwein... 6 2.2.1 - Versão Seqüencial... 6 2.2.2 - Versão Paralela... 7 2.3.1 - Versão Seqüencial... 8 2.3.2 - Versão Paralela... 9 3 - Resultados Obtidos... 9 4 - Ferramentas utilizadas e Metodologia de Execução dos Experimentos...10 5 - Forma de Execução... Erro! Indicador não definido. 6 - Conclusão...11 7 - Bibliografia...13
1 - Introdução O número Pi é a constante matemática que representa a relação entre perímetro e diâmetro circular. Apesar de ser conhecido há milhares anos, ainda é fonte de pesquisas em diversas áreas. Suas propriedades continuam a ser investigadas e busca-se frequentemente desenvolver novos e mais poderosos métodos para calcular seu valor. Em nosso trabalho, utilizaremos três algoritmos para o cálculo aproximado do PI. São eles: Gauss-Legendre, Borwein e Monte Carlo. 1.1 - Gauss-Legendre O algoritmo de Gauss-Legendre é um método de aproximações sucesivas. É baseado no trabalho de Carl Friedrich Gauss(1779-1815) e Adrien-Marie Legendre (1799-1855) combinado com algoritmos modernos para multiplicação e raízes quadradas. Em 2002 foi utilizado por Yasumasa Kanada para obter o recorde mundial no cálculo de casas decimais de pi. É um método considerado rapidamente convergente. Ele produz 45 milhões de dígitos corretos do utilizando apenas 25 iterações (a cada inretação a precisão dobra). Sua desvantagem é a necessidade de grande quantidade de memória para sua execução. 1.2 - Borwein O algoritmo de Borwein é um método numérico de aproximação com crescimento quadrático da precisão do. Obteve-se pouca informação a seu respeito e dentre os três algoritmos é o menos conhecido,. 1.3 - Monte Carlo O Método de Monte Carlo é um método para aproximação por meio da utilização de simulações estocásticas.
Possui diversas aplicações em diferentes áreas científicas e tem sido utilizado há bastante tempo para obter aproximações numéricas de funções complexas. Em nosso trabalho utilizaremos este método para a aproximação do valor do número Pi. 2 - Desenvolvimento 2.1 - Gauss-Legendre 2.1.1 - Versão Seqüencial Inicialização das variáveis: A cada iteração: O valor de será aproximado por: 2.1.2 - Versão Paralela Inicialização das variáveis:
A cada iteração separamos em três threads para todo n > 0. Serão registrados os valores de e a cada iteração para podermos paralelizar o cálculo de. Primeira Thread (Thread A): Segunda Thread (Thread B): Terceira Thread (Thread T): O valor de será aproximado por:
Figura 1. Representação gráfica da estratégia de paralelismo utilizada no algoritmo de Gauss-Legendre 2.2 - Borwein 2.2.1 - Versão Seqüencial Inicialização das variáveis: Para cada iteração: A aproximação de feita por interações será aproximado por:.
2.2.2 - Versão Paralela A inicialização das duas primeiras variáveis foi a mesma, e para sua paralelização o algoritmo foi quebrado em blocos dependentes, sendo esses blocos: Primeiro Termo de A (Thread A1): Segundo Termo de A (Thread A2): Cálculo de A (Thread A): Cálculo de Y (Thread Y): Como o cálculo de é independente de, foi utilizada a estratégia de produtor vs consumidor em que é gerado e, paralelamente, consumimos para calcular o valor de. Nesta estratégia utilizamos um buffer circular com capacidade de três váriaveis de ponto flutuante da biblioteca GMP. Para sincronizar as threads foi utilizado um vetor de mutex de tamanho três. A sincronia das threads funciona a partir da seguinte estratégia: ao finalizar uma operação sobre uma região crítica (RC) do buffer, a thread requisita o bloqueio da próxima região crítica para poder atuar sobre a mesma liberando a RC atual apenas se ou quando a próxima RC for liberada para uso(por isso, se utilizarmos apenas um buffer de tamanho dois as threads entrarão em deadlock). Note que a thread Y deve ser executada primeiramente visto que, ao mesmo tempo, esta deve bloquear a execução da thread A caso esta tente acessar região crítica utilizada pela thread Y ou as RCs posteriores.
Figura 2. Representação gráfica da estratégia de paralelismo utilizada no algoritmo de Borwein. 2.3 - Monte Carlo 2.3.1 - Versão Seqüencial Inicialização das variáveis: Para cada iteração faz-se: A aproximação de será dada por:
2.3.2 - Versão Paralela Dividiu-se os cálculos e testes estatísticos entre todos os nós disponiveis visto que este algoritmo é bastante paralelizável já que possui poucas dependências; seu resultado foi apenas acumulado na hora de calcular o número aproximado de PI. Figura 3. Representação gráfica da estratégia de paralelismo utilizada no algoritmo de Monte Carlo. 3 - Resultados Obtidos Dos três algoritmos estudados obtivemos os seguintes speed-ups: Resultados comparados no site Gauss-Legendre: Tempo Sequencial Paralelo T1(s) 146,739 96,194 T2(s) 146,373 96,022 T3(s) 146,689 96 077 T Méd 146,598 96,097 Desvio Padrão 0,197 0,087 Borwein:
Tempo Sequencial Paralelo T1(s) 185,616 198,008 T2(s) 185,600 197,964 T3(s) 185,716 194,896 T Méd 185,644 196,956 Desvio Padrão 0,062 1,784 Monte Carlo: Como o método de monte carlo não converge, utilizamos 100000000 iterações para estipular se há ou não maior eficiência do algoritmo executado paralelamente em relação ao sequencial. Tempo Sequencial Paralelo T1(s) 17,349 36,614 T2(s) 17,381 34,986 T3(s) 17,501 35,339 T Méd 17,410 35,646 Desvio Padrão 0,080 0,856 4 - Ferramentas utilizadas 4.1- Software Devido a precisão em número de casas decimais ser muito alta, os tipos de dados básicos oferecidos na linguagem c não foram suficientes. Para resolver essa questão utilizamos em todas as soluções a biblioteca GMP (The GNU Multiple Precision Arithmetic Library), a qual nos deixou como limitante em uma operação
aritmética apenas a quantidade de memória disponível em um computador. Para o problema alocamos uma memoria de tamanha 33300000 bits (aproximadamente, onde n é o tamanho da precisão que desejamos atingir, que no caso é 10.000.000 de digitos decimais). Para a programação distribuída foi utilizada a API Posix Pthreads que facilita a manipulação, sincronização e criação de threads. 4.2 Hardware Para a realização do calculo do speed-up utilizamos uma de nossas máquinas. Apesar de também termos rodádos os programas no Cluster, não vimos necessidades do calculo do tempo ser realizado no mesmo, pois nossos programas apenas rodária em uma unica máquina. Máquina utilizada: Modelo: HP DV6636 Processador: AMD Turion X2 64bits Dual Core 1.9GH:: Memória: 2GB de ram DD2:: Placa de Vídeo Dedicada: Vídeo NVIDIA GeForce Go 7150 S0: Ubuntu 11.10 5 - Conclusão O algoritmo de Monte Carlo foi o que apresentou pior speed-up. Isto se deve pois as threads não foram paralelizadas em nós independentes e sim executadas em um mesmo processador com dois núcleos. Mesmo possuindo dois núcleos, o escalonamento das threads é gerenciado pelo sistema operacional (SO) que, por motivos intrínsecos deste, não paralelizou o programa da forma desejada. Sendo assim, apesar de se possuir threads independentes, perde-se muito tempo na mudança de contexto feita pelo SO devido o escalonamento das mesmas (um tempo considerável se compararmos com o tempo de execução das simples operações definidas em cada thread). Outro ponto importante do algoritmo de monte carlo é que
por ser um método estatístico e utilizar elementos pseudo-randômicos em seu algoritmo, sua precisão se torna menos confiável. Por motivos semelhantes ao do algoritmo de Monte Carlo, o método de Borwein não apresentou o desempenho desejado ao ser executado de forma paralela visto que seu tempo de execução com a utilização de threads se comportou de forma semelhante ao mesmo algoritmo executado de forma seqüencial. Pode-se perceber também que, apesar da estratégia utilizada neste algoritmo para sua paralelização ser promissora, as threads devem ser definidas de forma que possuam tempo de execução similares fazendo com que as mesmas não se tornem gargalos. O algoritmo Gauss-Legendre paralelo possuiu o melhor speed-up dos três algorítmos já que sua paralelização foi implantada de uma forma em que presencia-se menos gargalos na execução e em que as cargas divididem-se de forma mais homogênea entre as threads. Consideramos satisfatórios nossos resultados obtidos, levando-se em consideração o estudo de diferentes estratégias e abordagens de paralelização. Em todos os algoritmos a forma sequencial mostrou-se mais fácil de se implementar do que a paralela já que foi necessária uma mudança do paradigma lógico sequencial para o paralelo. Dentre as dificuldades enfrentadas pelo grupo, podemos citar: 1) encontrar o algoritmo correto a ser implementado. Esta tarefa nos fez ter um retrabalho considerável em boa parte de nossa implementação. 2) A não-trivialidade na paralelização dos algoritmos de Borwein e de Gauss-Legendre que em muitas soluções propostas o ganho obtido aparentava-se nulo, 3) Problemas com sincronização de threads já que a não-trivialidade na paralelização exigia complexas estratégias, 4) Dificuldade na implementação devido a grande quantidade de fatores influênciadores tais como a grande quantidade de memória utilizada pelos pontos flutuantes da biblioteca GMP fazendo com que o programador adote uma atenção maior no uso da memória. A maioria dos problemas encontrados se deve principalmente ao fato dos integrantes do grupo estarem condicionados a pensar em algoritmos sequenciais.
6 - Bibliografia http://www.mat.ufrgs.br/~portosil/aplcom1a.html http://gmplib.org/ http://en.wikipedia.org/ http://pt.wikipedia.org/ https://computing.llnl.gov/tutorials/pthreads/ http://www.numberworld.org/digits/pi/