Issuu on Google+

FACULDADE INDEPENDENTE DO NORDESTE – FAINOR CURSO DE ENGENHARIA DA COMPUTAÇÃO

GILMAR PRADO PAIVA

RESOLUÇÃO DE SISTEMAS LINEARES UTILIZANDO OPENMP

VITÓRIA DA CONQUISTA - BA NOVEMBRO - 2013


FACULDADE INDEPENDENTE DO NORDESTE – FAINOR GILMAR PRADO PAIVA

RESOLUÇÃO DE SISTEMAS LINEARES UTILIZANDO OPENMP

Monografia apresentada como um dos requisitos para obtenção do titulo de Bacharel em Engenharia do curso de Engenharia da Computação da Faculdade Independente do Nordeste, Vitória da Conquista - BA. Orientador: Prof. Me. Stênio Longo Araújo.

VITÓRIA DA CONQUISTA – BA NOVEMBRO – 2013


P149r

Paiva, Gilmar Prado Resolução de sistemas lineares utilizando OPENMP./ Gilmar Prado Paiva_ _ Vitória da Conquista, 2013. 82 f. Orientador(a): Prof. Stenio Longo Monografia (Graduação) – Faculdade Independente do Nordeste, Curso de Engenharia de computação, 2013. 1. Computação de alto desempenho 2.OPENMP I. Título. CDD 004.06

Catalogação na fonte: Biblioteca da Fainor


AGRADECIMENTOS

Em primeiro lugar agradeço a Deus, que me deu inspiração, perseverança e coragem para chegar ao término deste trabalho. Aos meus pais João Paiva (in memorian) e Janita (in memorian) por incentivar e facilitar todos os processos para que este sonho tornasse realidade. A minha esposa Eliana por estar sempre ao meu lado me apoiando e incentivando em todos os momentos de dificuldade. Aos meus filhos Melques e Milena pelos momentos de indagação, compreensão e carinho durante todo este percurso. Ao meu orientador Stênio Longo, pela atenção e auxilio destinados a este trabalho. Ao meu professor de TCC II, Francisco Carvalho, pela sua paciência e disposição.


RESUMO

No presente trabalho, foram estudados conceitos importantes da Computação de Alto Desempenho (CAD). Dentro deste contexto, foi realizado um estudo para verificar o desempenho de algoritmos de resoluções de sistemas lineares utilizando o modelo de programação OpenMP. Para obtenção dos resultados, realizou-se uma série de testes em processadores multi core (processador Intel® Coretm 2 Duo 4 GB de memória de RAM 800Mhz DDR2 e processador Intel® Coretm I5 4GB de memória RAM 2,5 GHz). Para possibilitar a realização dos testes, foi desenvolvida uma aplicação, dentro do ambiente Linux, utilizando o compilador GCC da GNU. O compilador permitiu a paralelização de parte do código seqüencial, determinando um ganho de desempenho das aplicações. Após a execução de cinco vezes de cada aplicação, ficou constatado que houve um ganho de desempenho nos códigos quando são paralelizados. Também se constatou que o processador que possui uma quantidade maior de Core, obteve um ganho maior de desempenho.

Palavras-Chave: Computação de Alto Desempenho. OpenMp. Sistemas Lineares.


ABSTRACT

important concepts of High Performance Computing ( CAD ) were studied . Within this context, a study was conducted to verify the performance of algorithms resolutions of linear systems using the OpenMP programming model . To obtain the results , There was a series of tests on multi-core processors ( Intel 速 CoreTM 速 CoreTM I5 4 GB 4GB RAM 2.5 GHz processor 2 Duo Memory DDR2 800Mhz RAM and Intel ). To enable the tests , an application was developed within the Linux environment using the GNU GCC compiler. The compiler allowed the parallelization of sequential code , determining a gain in performance applications. After running five times for each application , it was found that there was a gain in performance when parallelized codes . It was also found that the processor that has a greater amount of Core , achieved a higher performance gain .

Key-Words: High Performance Computing. OpenMP. Linear Systems.


LISTA DE FIGURAS

Figura

1:

Estrutura

básica

de

um

multiprocessador

com

memória

compartilhada............................................................................................................ 24 Figura 2: Arquitetura com memória distribuída........................................................ 25 Figura 3: O fork-join modelo de programação suportado pelo OpenMP.................. 30 Figura 4: Exemplo “Hello, Word!”............................................................................. 31 Figura 5: Formato padrão da diretiva....................................................................... 31 Figura 6: Código de ilustração da condicional de compilação................................. 32 Figura 7: Sintaxe do Construtor Paralelo................................................................. 32 Figura 8: Cláusulas que podem ser usadas juntamente com o construtor.............. 33 Figura 9: Código para realizar a soma de três vetores............................................ 34 Figura 10: Código para soma de vetores................................................................. 36 Figura 11: Soma de vetores com a utilização da cláusula dynamic......................... 37 Figura 12: Exemplo de um construtor ordered......................................................... 38 Figura 13: Resultado do processo após a utilização da cláusula Ordered.............. 38 Figura 14: Exemplo do uso da cláusula Nowait....................................................... 38 Figura 15: Exemplo do código que define 4 threads que serão executada na região paralela...................................................................................................................... 39 Figura 16: Sintaxe do construtor sections................................................................ 39 Figura 17: O exemplo do construtor sections........................................................... 40 Figura 18: Sintaxe da diretiva single........................................................................ 41 Figura 19: Exemplo será utilização o construtor single............................................ 41 Figura 20: Sintaxe do construtor critical................................................................... 42 Figura 21: Funcionamento de uma região crítica..................................................... 43 Figura 22: Exemplo da utilização da diretiva critical................................................ 43 Figura 23: Esquema ilustrativo de uma barreira...................................................... 44 Figura 24: Exemplo da sincronização com barreiras............................................... 44 Figura 25: Sistema Linear Original........................................................................... 46 Figura 26: Produto Matriz......................................................................................... 48 Figura 27: O algoritmo do Produto Matriz Vetor....................................................... 49 Figura 28: O algoritmo com as implementações das diretivas do OpenMP............ 50


Figura 29: O algoritmo do Produto Matriz Vetor em OpenMP e com as devidas correções................................................................................................................... 51 Figura 30: Sistema linear após transformações....................................................... 52 Figura 31: Algoritmo Estruturado do Método Iterativo de Jacobi............................. 54 Figura 32: Algoritmo de Jacobi com as implementações das diretivas do OpenMP..................................................................................................................... 55 Figura 33: Formula de Recorrência de Gauss-Seidel............................................. 56 Figura 34: Algoritmo Estruturado do Método Iterativo de Gauss-Seidel................. 57 Figura 35:

Algoritmo de Gaus-Seidel com as implementações das diretivas do

OpenMP..................................................................................................................... 58 Figura 36: Aplicação para medir o desempenho da multiplicação de Matrizes e resolução de sistemas lineares................................................................................. 59 Figura 37: Algoritmo para cálculo do tempo............................................................ 62 Figura 38: Aplicação em funcionamento testando um método interativo................ 62


LISTA DE TABELAS

Tabela 1: Método Iterativo de Jacobi – Dez Iterações – Cinco vezes consecutivas.............................................................................................................. 60 Tabela 2: Desempenho da multiplicação de matrizes - processador Intel® Coretm 2 Duo 4 Gb de memória de ram 800Mhz DDR2.......................................................... 63 Tabela 3: Desempenho da multiplicação de matrizes - processador Intel® Coretm I5 4GB de memória ram 2,5 GHz.................................................................................. 64 Tabela 4: Desempenho do método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm 2 Duo 4 Gb de memória de ram 800Mhz DDR2........................................................................................................... 66 Tabela 5: Desempenho do método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm I5 4GB de memória ram 2,5 GHz..... 67 Tabela 6: Desempenho do método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm 2 Duo 4 Gb de memória de ram 800Mhz DDR2............................................................................................... 69 Tabela 7: Desempenho do método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm I5 4GB de memória ram 2,5 GHz..................................................................................................................... 70


LISTA DE GRÁFICOS Gráfico 1: Desempenho da multiplicação de matrizes - processador Intel® Coretm 2 Duo 4 Gb de memória de ram 800Mhz DDR2.......................................................... 63 Gráfico 2: Desempenho da multiplicação de matrizes - processador Intel® Coretm I5 4GB de memória ram 2,5 GHz.................................................................................. 64 Gráfico 3: Multiplicação de matrizes - Comparação de Desempenho Intel® Coretm I5 4GB de memória RAM 2,5 GHz x Intel® Coretm 2 Duo 4 GB de memória de RAM 800Mhz DDR2............................................................................................................65 Gráfico 4: Desempenho do método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm 2 Duo 4 Gb de memória de ram 800Mhz DDR2........................................................................................................... 66 Gráfico 5: Desempenho do método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm I5 4GB de memória ram 2,5 GHz..... 67 Gráfico 6: Método iterativo de Jacobi

- Comparação de Desempenho Intel®

Coretm I5 4GB de memória RAM 2,5 GHz x Intel® Coretm 2 Duo 4 GB de memória de RAM 800Mhz DDR2............................................................................................. 68 Gráfico 7: Desempenho do método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm 2 Duo 4 Gb de memória de ram 800Mhz DDR2............................................................................................... 69 Gráfico 8: Desempenho do método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5 - processador Intel® Coretm I5 4GB de memória ram 2,5 GHz..................................................................................................................... 70 Gráfico 9: Método iterativo de Gauss-Seidel - Comparação de Desempenho Intel® Coretm I5 4GB de memória RAM 2,5 GHz x Intel® Coretm 2 Duo 4 GB de memória de RAM 800Mhz DDR2............................................................................................. 71


LISTA DE ABREVIATURAS E SIGLAS

ARB

Architecture Review Board

API

Application Program Interface

CPU

Central Processing Unit

GOMP

Gnu OpenMP

GPU

Graphics Processing Unit

GCC

GNU C Compiler. Compilador da Linguagem C

ICC

Intel C/C++ Compiler

MISD

Multiple Instruction Single Data

MIMD

Multiple Instruction Multiple Data

MPI

Message Passing Interface

NUMA

Non-Uniform Memory Access

OPENMP

Open Multi-Processing

SISD

Single Instruction Single Data

SIMD

Single Instruction Multiple Data

UMA

Uniform Memory Access


SUMÁRIO

1 INTRODUÇÃO .................................................................................................. 15 1.1 CONTEXTUALIZAÇÃO E PROBLEMATIZAÇÃO .......................................... 16 1.2 PROBLEMA DE PESQUISA .......................................................................... 16 1.3 HIPÓTESES ................................................................................................... 17 1.4 OBJETIVOS GERAIS ..................................................................................... 17 1.4.1 OBJETIVOS ESPECÍFICOS ....................................................................... 17 1.5 JUSTIFICATIVA ............................................................................................. 17 1.6 MÉTODOS DE PESQUISA ............................................................................ 18 1.7 TRABALHOS RELACIONADOS .................................................................... 20 1.8 ESTRUTURA DA MONOGRAFIA .................................................................. 20 2 OPENMP ........................................................................................................... 21 2.1 ARQUITETURA PARALELA .......................................................................... 21 2.2 ARQUITETURAS COM MEMÓRIA COMPARTILHADA ................................ 23 2.3 ARQUITETURAS COM MEMÓRIA DISTRIBUÍDA ........................................ 24 2.4 INTRODUÇÃO AO OPENMP......................................................................... 26 2.4.1 HISTÓRIA DO OPENMP............................................................................. 27 2.4.1.1 PARALELISMO DE DADOS ........................................................................ 28 2.4.1.2 PARALELISMO DE LAÇOS ......................................................................... 28 2.4.1.3 PARALELISMO DE TAREFAS ..................................................................... 28 2.4.2 CONSIDERAÇÕES SOBRE O DESEMPENHO DO OPENMP .................. 29 2.4.3 MODELO DE PROGRAMAÇÃO DO OPENMP .......................................... 30 2.4.4 DIRETIVAS DE COMPILAÇÃO ....................................................................... 31 2.4.4.1 CONDICIONAL DE COMPILAÇÃO .......................................................... 31 2.4.4.2 CONSTRUTOR PARALELO .................................................................... 32 2.4.4.3 DIRETIVA PARALLEL .............................................................................. 34 2.4.4.4 DIRETIVA FOR ........................................................................................ 35 2.4.4.5 DIRETIVA SECTIONS.............................................................................. 39 2.4.4.6 DIRETIVA SINGLE ................................................................................... 41 2.4.4.7 DIRETIVAS DE SINCRONIZAÇÃO .......................................................... 42 3 SISTEMAS LINEARES ........................................................................................ 46 3.1 PRODUTO MATRIZ ....................................................................................... 48 3.1.1 ALGORÍTMO SEQUENCIAL ....................................................................... 49 3.1.2 ALGORITMO PARALELO EM OPENMP ................................................. 50 3.2 MÉTODO ITERATIVO DE JACOBI ................................................................ 52 3.2.1 ALGORITMO ESTRUTURADO DO MÉTODO DE JACOBI ........................ 53 3.3 MÉTODO ITERATIVO DE GAUSS-SEIDEL .................................................. 56 3.3.1 ALGORITMO ESTRUTURADO DO MÉTODO DE GAUSS-SEIDEL .......... 57 4 RESOLUÇÃO DE SISTEMAS LINEARES UTILIZANDO OPENMP ................ 59 4.1 AMBIENTES DE TESTES .............................................................................. 60 4.1.1 ANÁLISE DO DESEMPENHO .................................................................... 61


5 CONCLUSÃO ................................................................................................... 72 5.1 SUGESTÕES PARA TRABALHOS FUTUROS ............................................. 72 REFERÊNCIAS .................................................................................................... 73 APÊNDICE A ....................................................................................................... 75 APÊNDICE B ....................................................................................................... 77 APÊNDICE C ....................................................................................................... 79 APÊNDICE D ....................................................................................................... 81


15

1 INTRODUÇÃO

Tem se observado nos últimos anos que os processadores disponíveis no mercado atingiram freqüências de clock próximas do limite suportado pelas atuais tecnologias provocando um alto aquecimento na CPU, o que tem dificultado um avanço vertical no crescimento de sua velocidade. Existem diversos fatores que contribuíram para atingir este nível, destacando-se, dentre eles, o aumento do consumo de energia e seus efeitos colaterais, como dissipação térmica e fenômenos de capacitância e indutância. (PATTERSON, 2005). A tecnologia de semicondutores avançou rapidamente nas últimas décadas. Os processadores se tornaram cada vez mais rápidos. Grandes empresas de processadores têm a necessidade de continuar produzindo com uma performance em maior qualidade e quantidade. Da forma como foi conduzida nos últimos anos, o aumento de desempenho dos processadores com um único núcleo decorre basicamente de dois fatores: a diminuição do tamanho de seus componentes e o aumento da velocidade do clock do processador. Estes componentes físicos alcançaram um tamanho tão pequeno que não poderão mais diminuir de tamanho. E por isso que o uso de paralelismo nas arquiteturas é uma estratégia para superar essas limitações físicas. Nesse contexto, a solução encontrada pelas fabricantes tem sido colocar diversos processadores em um único processador, criando os chamados processadores multi-core. Esse mecanismo deu início à popularização das arquiteturas paralelas entre os usuários domésticos. Ao utilizar o paralelismo existente

nos

programas,

o

processador multi-core

apresenta

ganho

em

desempenho baseado no número de cores que oferece, e não no tamanho de seus componentes ou clock do processador (PATTERSON, 2005). Para atender a demanda destes processadores multi-core, é necessário que seja feito um desenvolvimento da aplicação voltada para o uso integral e escalável destes processadores. Afinal, que utilidade teria um processador multi-core para um programa seqüencial executando uma thread de cada vez? Uma aplicação escrita em um programa de forma seqüencial nas APIs atuais precisa ser totalmente adaptada para uma máquina com mais processadores. Isto traz uma dificuldade que impede o usuário de usufruir plenamente dos ganhos de desempenho trazidos pelos processadores multi-core (PATTERSON, 2005).


16

Existem várias soluções existentes no mercado que são utilizadas para suprir esta necessidade (PTHREADS, MPI, GPU), dentre elas, destaca-se o padrão OpenMP, que é o modelo de programação utilizado em memória compartilhada, permitindo a todas as tarefas um acesso ao dados existentes na memória global. Através da aplicação de diretivas sobre o código já existente, o usuário do OpenMP não precisa modificar radicalmente sua aplicação para contar com os benefícios de um ambiente multitarefa. Pois esta Application

Programming Interface (API) oferece suporte para programação paralela, que utilizam as técnicas de memória compartilhada em ambientes que possuem vários núcleos de processamento compartilhado com o mesmo recurso de memória.

O modelo de programação adotado pelo OpenMP é bastante portável e escalável, podendo ser utilizado numa gama de plataformas que variam desde um computador pessoal até supercomputadores. E por estes requisitos, foi a ferramenta adequada para resolver sistemas lineares e obter um ganho de desempenho na aplicação

1.1 CONTEXTUALIZAÇÃO E PROBLEMATIZAÇÃO

A demanda atual por poder computacional cada vez maior que sempre está acima dos recursos disponíveis, sendo que a computação científica é o principal mercado para a resolução de problemas altamente complexos de processamentos numéricos, armazenamento de dados e visualização de dados que exigem um alto poder de processamento.

1.2 PROBLEMAS DE PESQUISA

É possível reduzir o tempo de execução na resolução de um sistema linear utilizando OpenMP?


17

1.2 HIPÓTESES

Por exigir uma alta demanda computacional, os algoritmos na resolução de sistemas lineares utilizando OpenMP tem melhor desempenho, ou seja, são executados em menor tempo do que os algoritmos sem paralelismo.

1.4 OBJETIVO GERAL

Analisar o desempenho de algorítmos na resolução de sistemas lineares.

1.4.1 OBJETIVOS ESPECÍFICOS

Realizar um estudo do OpenMP

Realizar um estudo sobre sistemas lineares

Aplicar os testes e avaliar os resultados.

1.5 JUSTIFICATIVA

Para atender a demanda da computação científica de alto desempenho que precisa resolver processamentos numéricos altamente complexos (previsão do tempo e do clima, cálculos numéricos, simulação de jogos, processamento de imagens), armazenamento de dados e visualização de dados que exigem um processamento cada vez maior, é necessário que seja feito um desenvolvimento voltado para o uso integral e escalável de processadores com vários núcleos, permitindo um ganho de desempenho (CANAL DÚBITA, 2013).


18

1.6 MÉTODOS DE PESQUISA

A metodologia consiste em estudar, compreender e avaliar os vários métodos disponíveis para a realização de uma pesquisa acadêmica. A metodologia, em um nível aplicado, examina, descreve e avaliam métodos e técnicas de pesquisa que possibilitam a coleta e o processamento de informações, visando o encaminhamento e à resolução de problemas e/ou questões de investigação (CRISTIANO, 2013). Neste trabalho será utilizado o método dedutivo, de acordo com o entendimento clássico, é o método que parte do geral e, a seguir, desce ao particular. A partir de princípios, leis ou teorias consideradas verdadeiras e indiscutíveis, prediz a ocorrência de casos particulares com base na lógica. “Parte de princípios reconhecidos como verdadeiros e indiscutíveis e possibilita chegar a conclusões de maneira puramente formal, isto é, em virtude unicamente de sua lógica.” (GIL, 2008, p. 9).

Para obter um entendimento melhor do assunto em questão, foi realizado uma série de testes em processadores multi core (Intel® Coretm 2 Duo 4 GB de memória de ram 800Mhz DDR2 e Intel® Coretm I5 4GB de memória RAM 2,5 GHz ), além disso, foi utilizada plataforma Linux, onde foi possível determinar um ganho de performance naquelas máquinas com uma quantidade maior de núcleo, e nos processos que puderam ser paralelizados totalmente ou parcialmente. Para possibilitar a realização dos testes, foi necessária a utilização de ferramentas para o desenvolvimento da aplicação. Foi utilizado o compilador g++ (linguagem C++), e o complilador gcc (compilador C) ambos já vem instalado com os pacotes. Para avaliar o alto desempenho no controle dos experimentos que seguem, será utilizada uma métrica chamada Speedup. O Speedup representa a razão entre o tempo de execução de um programa executado em paralelo e o tempo de execução de sua versão seqüencial. Por isso, trata-se de uma boa medida para avaliar quantitativamente o ganho de desempenho trazido pela versão paralela de um programa em relação à sua versão seqüencial. Abaixo a fórmula do Speedup:

Su =






19

Onde: u:

É o número de unidades de execução utilizadas pelo programa.

T1: É o tempo de execução do programa seqüencial. Tu: É o tempo de execução da versão paralela executada em um ambiente com u unidades de execução disponíveis As fórmulas dos cálculos do Speedup estão apresentadas na seção 2.4.2 deste trabalho.

Quanto

à

abordagem

pode-se

classificar

como

uma

pesquisa

qualitativa/quantitativa por ambas serem comuns em ciências exatas. Os dados coletados serão analisados por meios estatísticos e podem ser visualizados e compreendidos de acordo as amostragem dos fatos. •

A Pesquisa Científica visa a conhecer cientificamente um ou mais aspectos de determinado assunto. Para tanto, deve ser sistemática, metódica e crítica. O produto da pesquisa científica deve contribuir para o avanço do conhecimento humano. Na vida acadêmica, a pesquisa é um exercício que permite despertar o espírito de investigação diante dos trabalhos e problemas sugeridos ou propostos pelos professores e orientadores (CRISTIANO, 2013).

“Destacamos que “o planejamento de uma pesquisa depende tanto do problema a ser estudado, da sua natureza e situação espaço-temporal em que se encontra, quanto da natureza e nível de conhecimento do pesquisador.” (KÖCHE, 2007, p. 122). •

Quanto ao tipo, é considerada uma pesquisa aplicada, pois o objetivo é gerar novos conhecimentos para aplicação prática dirigidos à solução de problemas específicos.

Quanto à estratégia no que se refere à fonte de informação, é classificada como documental, pois, todos os registros que são pesquisados forma um documento.


20

1.7 TRABALHOS RELACIONADOS

ZULIAN (2006) apresentou um trabalho visando analisar algumas das principais estruturas do OpenMP e executar benchmarks para verificar o ganho de desempenho obtido com seu uso. Com o propósito de verificar o impacto que a utilização de um compilador específico exerce no desempenho do OpenMP, os benchmarks foram executados nos compiladores ICC da Intel e GCC da GNU. PRETTI e FASSARELLA (2010), contextualizou alguns conceitos da computação de alto desempenho (CAD) e fez um estudo sobre a tecnologia CUDA (Compute Unified Device Architecture). Bonat e Giusti (2008), apresentou um artigo científico onde faz uma comparação quanto ao suporte OpenMp de três compiladores (Intel Compiler, GCC OpenMP e Sun Studio), e o nível de otimização obtidos por cada um deles.

1.7 ESTRUTURA DA MONOGRAFIA

O capítulo 1 é composto pela introdução do trabalho com definição de alguns conceitos e outros pontos importantes na delineação da pesquisa; O capítulo 2 é composto pela revisão bibliográfica, onde foi realizado um apanhado geral sobre a Arquitetura paralela e a API OpenMP. No capítulo 3 é feita uma revisão sobre os sistemas lineares, onde o foco principal são alguns métodos iterativos. No capítulo 4 é feito o desenvolvimento da aplicação que serão realizados os testes de desempenho. No capítulo 5 é feita a conclusão sobre o trabalho desenvolvido.


21

2

OpenMP

Este capítulo trata sobre o contexto científico atual relacionado à computação paralela. Primeiramente, serão mostradas as principais arquiteturas de hardware empregadas. Em seguida, serão apresentados os diferentes tipos de paralelismo encontrados em programas paralelos. Com isso, seguirá um apanhado das APIs de programação paralela com suporte a paralelismo de tarefas mais conhecidas, em seguida nas próximas seções serão abordados de uma maneira mais minuciosa a API OpenMP, cujo suporte ao paralelismo de tarefas será a base deste trabalho. A seção 2.4 apresenta a API, mostrando brevemente como a mesma surgiu e quais são seus objetivos. Em seguida, será dada uma visão geral da interface de programação e como é feito o processo de programação paralela mostrando como OpenMP aborda paralelismo de dados e de laços.

2.1 Arquitetura Paralela

O objetivo da computação paralela é utilizar de forma eficiente os múltiplos elementos de processamento disponíveis em sistemas computacionais modernos, a fim de resolver problemas complexos em menos tempo, quando comparados a soluções que empregam algoritmos seqüenciais. No entanto, a popularização dos sistemas com múltiplos elementos de processamento se deu apenas no início do século 21, motivada principalmente pela saturação dos recursos tecnológicos utilizados em mono processadores (PATTERSON, 2005). Os mono processadores tiveram grandes melhorias de performance no período de 1986 a 2002, porém, esta evolução esbarrou em limites físicos, como o calor gerado pelo crescente número de transistores necessários para aumentar a taxa de clock, e o alto consumo de energia elétrica, por isso, os engenheiros de computadores

começaram

a

buscar

outras

estratégias

para

aumentar

o

desempenho do hardware e uma melhor utilização do espaço disponível em um chip. Dada a utilidade limitada de adição de unidades funcionais, voltaram às idéias da década de 1980: vários processadores que compartilham memória são configurados em uma única máquina e, cada vez mais, em um chip. Esta nova geração de memória compartilhada em computadores paralelos é barata e é destinado para uso de finalidade geral (PATTERSON, 2005).


22

Além do fim comercial da era dos monos processadores, outros fatores também contribuíram para a evolução das arquiteturas paralelas, como: •

Interesse crescente em servidores de alto desempenho

Crescimento de aplicações que manipulam grande volume de dados

Esta idéia de utilizar múltiplas unidades de processamento para aumentar o desempenho e a disponibilidade dos recursos computacionais existe desde quando os primeiros computadores eletrônicos foram construídos. Segundo Flynn (1996), propôs um modelo para dividir computadores em categorias bem abrangentes, que ainda é útil, pois estabelece classes de computadores de acordo com sua capacidade de paralelizar instruções e dados. Através deste modelo, foram apresentadas as seguintes categorias: SISD - Single Instruction Stream Single Data Stream (Fluxo Único de Instruções Fluxo Único de Dados) - nesta classe, um único fluxo de instruções opera sobre um único fluxo de dados. Isto corresponde ao processamento sequencial característico da máquina de von Neumann e que compreende os computadores pessoais e estações de trabalho. Apesar dos programas estarem organizados através de instruções sequenciais, elas podem ser executadas de forma sobreposta em diferentes estágios. Arquiteturas SISD caracterizam-se por possuírem uma única unidade de controle podendo possuir mais de uma unidade funcional. SIMD - Single Instruction Stream Multiple Data Streams (Fluxo Único de Instruções Múltiplos Fluxos de Dados): - esta classificação corresponde ao processamento de vários dados sob o comando de apenas uma instrução. Em uma arquitetura SIMD o programa ainda segue uma organização sequencial. Para possibilitar o acesso a múltiplos dados é preciso uma organização de memória em diversos módulos. A unidade de controle é única e existem diversas unidades funcionais. Nesta classe estão os processadores vetoriais e matriciais. MISD - Multiple Instruction Streams Single Data Stream (Múltiplos Fluxos de Instruções Fluxo Único de Dados) - neste caso, múltiplas unidades de controle executando instruções distintas operam sobre o mesmo dado. Esta classe, na realidade, não representa nenhum paradigma de programação existente e é impraticável tecnologicamente. MIMD - Multiple Instruction Streams Multiple Data Streams (Múltiplos Fluxos de Instruções Múltiplos Fluxos de Dados)- esta classe é bastante genérica envolvendo o processamento de múltiplos dados por parte de múltiplas instruções.


23

Neste caso, várias unidades de controle comandam suas unidades funcionais, as quais têm acessos a vários módulos de memória. Qualquer grupo de máquinas operando como uma unidade (deve haver certo grau de interação entre as máquinas) enquadra-se como MIMD. Alguns representantes desta categoria são os servidores multiprocessados, as redes e estações e as arquiteturas massivamente paralelas. Processadores MIMD são divididos em dois grupos, que são determinados de acordo com a organização da memória de trabalho e a maneira como os diferentes elementos de processamento estão interconectados. Estes grupos são classificados como multiprocessadores (memória compartilhada) ou multicomputadores (memória distribuída). Onde o primeiro grupo pode também ser denominado de ambiente fortemente acoplado enquanto que o segundo fracamente acoplado. As máquinas denominadas

multiprocessadas

possuem,

como

principal

característica,

o

compartilhamento da memória entre todos os processadores (PATTERSON, 2005).

2.2 Arquiteturas com memória compartilhada

Para sistemas com um número relativamente pequeno de elementos de processamento, é possível que os mesmos compartilhem uma única memória. Arquiteturas com memória compartilhada trazem o benefício de prover um espaço de endereçamento único, o que torna a implementação de programas paralelos mais simples, pois, diferentemente do que acontece em arquiteturas com memória distribuída, a comunicação entre os elementos de processamento ocorre de forma natural, através da memória centralizada. Assim, o compartilhamento de dados entre unidades de execução de um programa paralelo se torna extremamente rápido. Para satisfazer a demanda de memória de todos os elementos de processamento, de forma a não sobrecarregar o barramento de comunicação, é necessário que cada um possua uma memória especial, pequena e de acesso rápido, chamada de memória cache, que é utilizada como um dispositivo de armazenamento temporário cujos dados são frequentemente acessados e seus tempos de acesso são normalmente rápidos. Isso faz com que o número de acessos à memória principal seja reduzido. A figura 1 ilustra esta arquitetura. Em arquiteturas de memória compartilhada, outra classificação que também merece destaque é a que define como os processadores acessam a memória.


24

Dessa forma, eles podem ser classificados como de arquitetura UMA (Uniform Memory Access), onde o tempo de acesso aos dados localizados em qualquer posição da memória é o mesmo para todos os processadores. A forma de interconexão mais comum neste tipo de máquina é o barramento e a memória geralmente é implementada em um único módulo. Ou arquitetura NUMA (NonUniform Memory Access), onde o tempo de acesso à memória depende da posição em que a mesma estiver localizada em relação ao processador. Isso acontece devido ao fato de que, existe um limite quanto ao número de processadores por barramento de memória. Dessa forma, um processador terá acesso mais rápido a uma determinada região de memória localizada no seu barramento do que a uma região localizada em um barramento diferente (PATTERSON, 2005). Figura 1: Estrutura básica de um multiprocessador com memória compartilhada.

Fonte: Autoria própria(2013)

2.3 Arquiteturas com memória distribuída

Neste grupo se enquadram os multiprocessadores que possuem a memória principal fisicamente distribuída entre os diferentes elementos de processamento. Esta arquitetura é adequada para suportar um número muito grande de elementos de processamento, pois, ao atribuir a cada elemento uma memória de trabalho própria, evita que todos disputem o mesmo recurso físico (memória). Esta disputa, quando excessiva, sobre carrega o barramento de comunicação entre os


25

processadores e a memória, gerando aumento de latência no acesso e conseqüente a queda de desempenho de todo o sistema. A figura 2 ilustra basicamente a arquitetura. Figura 2: Arquitetura com memória distribuída.

Fonte: Autoria própria(2013)

O grande número de elementos de processamento distribuídos em diferentes locais físicos exige uma rede de interconexão com alta largura de banda, a fim de minimizar a perda de desempenho decorrente das várias trocas de mensagens feitas durante o processamento de programas paralelos com alta taxa de comunicação entre as unidades de execução. Além disso, a implementação de mecanismos de sincronização de processos, ex: mutex, semáforos, monitores e variáveis de condição, devem considerar que os diferentes elementos de processamento estão interconectados através de uma rede que, tipicamente, utiliza o protocolo de comunicação TCP/IP, ou seja, as operações de sincronização e os acessos a dados comuns entre as unidades de execução poderão resultar em envios de mensagens via rede, ocasionando implementações mais complexas e computacionalmente custosas. Como a latência do acesso à memória depende da localização dos dados que estão sendo buscadas (um elemento de processamento pode estar buscando dados que se encontram em uma memória distante), as arquiteturas com memória distribuída também são conhecidas como non uniform memory access (NUMA), devido a essa não uniformidade do acesso à memória. Portanto, é necessário considerar este overhead na execução de programas paralelos em arquiteturas deste tipo

(PATTERSON, 2005).


26

2.4 INTRODUÇÃO AO OPENMP

OpenMP é uma Application Programming Interface (API) desenvolvida para facilitar a implementação de programas paralelos. Não se trata de um padrão sancionado oficialmente, mas sim de um acordo realizado entre membros da ARB, se tornando o OpenMP um modelo portátil e escalável, transformando a programação paralela em uma interface simples e flexível para o desenvolvimento de aplicações paralelas para todas as plataformas que vão desde os desktops até os supercomputadores. Projetada para ser uma API escalável e portável, o OpenMP adiciona uma camada de abstração acima do nível das threads, deixando o programador livre da tarefa de criar, gerenciar e destruir threads. O sucesso da API OpenMP pode ser atribuída a uma série de fatores. Uma é a sua forte ênfase em programação paralela estruturada, é relativamente simples de usar e pode ser executada em muitas plataformas diferentes. Utilizando as diretivas #pragma disponíveis na linguagem, o programador explicita os locais do programa que serão paralelizados. A etapa de pré-compilação do programa converte as diretivas em chamadas para threads comuns. O programador não precisa usar as APIs de threads diretamente, isto é feito pelo OpenMP. Isto traz portabilidade aos sistemas desenvolvidos, pois mesmo as APIs de threads mais abrangentes costumam apresentar diferenças em arquiteturas diferentes. O OpenMP apresenta ainda a possibilidade de determinar junto ao sistema o número de núcleos ou processadores disponíveis. Assim, um mesmo código em OpenMP pode ser aproveitado em um sistema com um, dois, quatro núcleos ou mais, sem precisar ser reescrito. Cabe ao compilador adaptar o código existente para criar o maior número de threads possível (CHAPMAN e JOST, 2008).


27

2.4.1 História do OpenMP No início dos anos 90, os fabricantes de multiprocessadores com memória compartilhada forneciam extensões para a linguagem de programação Fortran, com a intenção de prover aos desenvolvedores meios de paralelizar laços em programas sequenciais. Estas extensões traziam diretivas que delegavam ao compilador a tarefa

de

paralelizar

automaticamente

tais

laços.

Porém,

havia

muitas

implementações divergentes mas com funcionalidades equivalentes. Não existia um padrão. A partir disso, em 1994 ocorreu a primeira tentativa de estabelecer um conjunto de extensões padronizado para auxiliar a implementação de programas paralelos em Fortran, o que foi chamado de ANSI X3H5. Este padrão foi amplamente adotado para compilar programas paralelos com a finalidade de serem executados em arquiteturas com memória distribuída, seguindo a popularidade que estas máquinas estavam obtendo na época. Assim, foi aberto o caminho para o desenvolvimento de um padrão a ser utilizado por compiladores para arquiteturas com memória compartilhada e, em 1997, foi iniciada a especificação da norma OpenMP. O projeto foi conduzido pela OpenMP ARB (ou apenas "ARB" - Architecture Review Board), que é a organização sem fins lucrativos que detém a marca e supervisiona a especificação OpenMP e produz e aprova novas versões da especificação. O ARB ajuda a organizar e financiar conferências, workshops e outros eventos relacionados, e promove a ferramenta, que originalmente é composta pelos seguintes membros permanentes: AMD, CAPS-Entreprise,Transmitir Computer, Cray, Fujitsu, HP, IBM, Intel, NEC, NVIDIA,

Oracle

Corporation,

Red

Hat,

O

Portland

Group,

Inc.,Texas

Instruments. É também composta por alguns membros auxiliares: ANL, ASC / LLNL, BSC, cOMPunity, EPCC, LANL, NASA, ORNL, RWTH Aachen University, SNL-Sandia National Lab, Texas Advanced Computing Center, Universidade de Houston. A primeira versão da API (1.0) foi especificada apenas para Fortran. Já no ano seguinte (1998) foram incorporadas diretivas para as linguagens C e C++. Desde então, novas versões foram criadas, trazendo novas diretivas e rotinas, bem como suporte a diferentes tipos de paralelismo, entre eles o paralelismo de tarefas, que foi introduzido na versão 3.0 em 2008. Atualmente, a norma encontra-se na versão 4.0, liberada em Março de 2013 (CHAPMAN e JOST, 2008).


28

2.4.1.1 Paralelismo de Dados

Acontece quando uma operação é aplicada sobre grandes quantidades de dados de forma paralela. Como exemplo pode citar uma soma de matrizes. Pois os elementos são somados de forma independente. Assim dadas as matrizes A e B, para obtermos a soma de ambas, armazenando o resultado em uma terceira matriz

C, podemos obter cada elemento Cij paralelamente, aplicando a operação de “soma” (uma operação) sobre todos os pares de elementos Aij, Bij (múltiplos dados) simultaneamente (OLIVEIRA, 2011).

2.4.1.2 Paralelismo de Laços

É similar ao paralelismo de dados, porém o mesmo se aplica quando queremos paralelizar operações contidas em um laço for, ou seja, um programa iterativo onde o número de repetições é conhecido com antecedência, mas que na haja dependência entre o resultado de cada processamento (OLIVEIRA, 2011).

2.4.1.3 Paralelismo de Tarefas

É utilizado para expressar o paralelismo de instâncias de um conjunto de instruções (tarefas).

A concorrência é especificada na forma de tarefas, que

executam conjuntos de instruções determinados pelo programa. De maneira que, por exemplo, uma rotina pode ser executada n vezes simultaneamente. E por conter este caráter genérico, este tipo de paralelismo pode ser utilizado para tarefas cujo número de execuções não pode ser previsto estaticamente, como acontece em programas recursivos e laços while (OLIVEIRA, 2011).

2.4.2 Considerações sobre o desempenho do OpenMP

O desempenho em sistemas paralelos envolve um conjunto de medidas totalmente diferente em relação aos sistemas sequenciais. Em um processo paralelo as principais medidas são o ganho de velocidade sobre um seqüencial (speedup) e a capacidade de crescimento (escalabilidade). O desempenho em qualquer sistema


29

computacional pode-se resumir em velocidade na execução das tarefas. Quanto mais råpido as aplicaçþes forem executadas, melhor serå o desempenho. Quanto a redução do tempo de execução pode ser esperado que a paralelização feita em OpenMP Ê, de fato uma paralelização por memória compartilhada. Se denotamos por T1 o tempo de execução de um aplicativo em um processador, então, em uma situação ideal, o tempo de execução em processadores P deve ser de S =





 

. Se o TP indica o tempo de execução em processadores P, a relação

, ĂŠ referido como o aumento de velocidade paralela e ĂŠ uma medida para

mostrar o sucesso da paralelização. No entanto, uma sÊrie de obståculos normalmente tem de ser ultrapassados antes que atinja um speedup ideal, ou algo próximo a isso. Geralmente em todos os programas existirão algumas regiþes que são adequadas para a paralelização e outras não. Ao utilizar um número crescente de processadores, o tempo gasto no processo serå reduzido, mas a secção seqßencial continua a ser a mesma. Eventualmente o tempo de execução da região paralela Ê maior que o cålculo da parte seqßencial. Este efeito, conhecido como a lei de Amdahl, que Ê uma fórmula que tenta explicar o ganho obtido em uma aplicação ao adicionar mais núcleos de processamento e podem ser formulados como:

G=







Onde S ĂŠ o tempo gasto executando a parte seqĂźencial do programa e n ĂŠ o nĂşmero de nĂşcleos de processamento. Suponha um programa que possui 60% de seu cĂłdigo puramente seqĂźencial, e os 40% restantes do cĂłdigo podem ser executados por mĂşltiplas threads. Se colocarmos este programa para executar em uma mĂĄquina com dois nĂşcleos, aplicando a lei de Amdahl teria o seguinte resultado:

G=

Portanto, o ganho serĂĄ de 25%.



. 

. 

=



.

= 1.25


30

Se o número de processadores ou núcleos tende ao infinito, o limite da

1 

fórmula tende a:

Ou seja, o ganho máximo que se pode obter depende da porção do código que não pode ser executada paralelamente. No Exemplo acima, onde 60% do código é



puramente seqüencial, o limite de ganho será de:

.

= 1.67

O que significa que o programa executará no máximo com um ganho de 67%, independente do número de núcleos que forem adicionados (AKHTER,2006). 2.4.3 Modelo de Programação do OpenMP Baseia-se na criação de várias threads (linhas de execução que compartilham o mesmo recurso na memória), onde o modelo de programação começa com um único segmento de execução, forma serial, por apenas uma thread chamada de master-thread. A partir de certo momento na execução do código, o programa entra na região paralela. Nesta etapa do processo, a execução do código se divide (FORK) e cada parte do processo é dividido entre um número arbitrário de threads, em um determinado ponto, essa threads, exceto a inicial deixam de existir, o final da região paralela é chamado de JOIN. A figura 3 ilustra o modelo FORK-JOIN e a figura 4 mostra o exemplo “Hello, Word” para este modelo de programação. Figura 3: O fork-join modelo de programação suportado pelo OpenMP.

Fonte: https://computing.llnl.gov(2013)


31

Figura 4: Exemplo “Hello, Word”

//** //** //** //** //**

Empresa: Fainor – Faculdade Ind. Do Nordeste Autor : Gilmar Prado Paiva Data : 20/12/2013 hello.c Exemplo Hello, Word

#include <omp.h> #include <stdio.h> int main (int argc, char *argv[]) { int nthreads, tid; printf("Hello World! Fora da Região Paralela\n"); /* Inicio da Região Paralela*/ #pragma omp parallel private(nthreads, tid) { /* Capta o número da Thread na Região paralela */ tid = omp_get_thread_num(); printf("Região Paralela. Thread = %d\n", tid); /* Executa somente a Thread Principal */ if (tid == 0) { nthreads = omp_get_num_threads(); printf("Total de threads = %d\n", nthreads); } } /* Fim da Região Paralela*/ printf("Hello World! Fora d Região Paralela.\n"); exit(0); }

A API OpenMP é composta por um conjunto de diretivas de compilador, biblioteca e variáveis de ambiente para especificar paralelismo de memória compartilhada. Uma diretiva OpenMP é um comentário especialmente formatado que geralmente se aplica ao código executável. Muitas das diretivas são aplicadas a um bloco de código estruturado, ou uma seqüência de comandos executáveis com uma única entrada na parte superior e uma única saída na parte na parte inferior em programas Fortran, e uma instrução executável em C / C++ (que pode ser composta por uma entrada e uma saída). Em outras palavras, o programa não pode ramificar para dentro ou fora dos blocos de códigos associados às diretivas. Em programas Fortran, no início e no final do bloco de código, aplica-se explicitamente as diretivas OpenMP, em C / C++, aplica-se apenas no início. Utilizando as diretivas OpenMP de forma correta é possível obter: •

Criação de threads para execução paralela,


32

Especificar a forma de compartilhar threads dentro um processo criado,

Declarar variáveis privadas e públicas, e

Sincronizar threads e capacitá-los para executar determinadas operações de forma exclusiva. A maneira como a divisão do processamento ocorrerá, bem como a

quantidade de threads que serão utilizadas e o momento em que a execução do programa será paralelizada depende das diretivas de programação utilizadas. Algumas diretivas serão apresentadas na seção seguinte (CHAPMAN e JOST, 2008).

2.4.4 Diretivas de Compilação As diretivas são inseridas diretamente no código em C/C++ e tem modos distintos de utilização. Todas elas recebem o termo pragma omp no início de sua chamada e tem a linha iniciada com o caractere “#”. A figura 5 mostra a sintaxe da diretiva. Figura 5: Formato padrão da diretiva

#pragma omp nome_da_diretiva [cláusula,...] novalinha

2.4.4.1 Condicional de compilação

De acordo com a figura 6, quando um código que contém diretivas do OpenMP é compilado por um compilador que não suporta o OpenMP ou quando não é habilitada, este simplesmente ignora as diretivas e compila o programa de forma seqüencial. Entretanto, esse código pode conter alguma instrução específica do OpenMP, como por exemplo, a chamada de funções que retornam informações do ambiente em questão e que estão definidas na biblioteca <omp.h>. Neste caso, o compilador não vai encontrar a definição dessas funções e o programa não será compilado. Para tornar possível a compilação de um programa OpenMP tanto na versão paralela quanto na versão seqüencial, pode-se utilizar a condicional de


33

compilação. Nesse caso, as funções ficam sob o controle de uma diretiva #ifdef OpenMP e só serão chamadas se essa macro estiver definida. Figura 6: Código de ilustração da condicional de compilação.

#ifdef _OPENMP #include <omp.h> #else if #define omp_get_thread_num() 0 #endif ... int id = omp_get_thread_num()

2.4.4.2 - Construtor Paralelo O construtor paralelo de acordo com a figura 7, é a diretiva mais importante do OpenMP, sendo que é o responsável pela localização da região paralela que será executada o código em paralelo. Se o construtor não for especificado o programa será executado de forma seqüencial.

Figura 7: Sintaxe do Construtor Paralelo.

#pragma omp parallel [cláusula,...] novalinha


34

Figura 8: cláusulas que podem ser usadas juntamente com o construtor.

if (espressão lógica) private (lista de variáveis) shared (lista de variáveis) firstprivate (lista de variáveis) default (shared | none) copyin (lista de variáveis) reduction (operador: lista de variáveis) num_threads (variável inteira)

Quando uma thread inicial encontra um construtor paralelo ela cria um grupo de threads que irão executar o código tornando esta thread, como a mestre do grupo. Porém, esse construtor não divide o trabalho entre as threads, apenas cria a região paralela. Dentro de uma região paralela, quando as threads encontram outro construtor paralelo, cada uma delas cria um novo grupo de threads e torna-se a thread mestre desse novo grupo. Essas regiões são denominadas regiões paralelas aninhadas e por padrão são executadas de forma seqüencial, ou seja, o novo grupo criado contém apenas uma thread, que e a própria thread mestre do grupo. Uma região paralela é chamada de inativa quando é executada por apenas uma thread e é chamada de ativa quando é executada por várias threads (CHAPMAN e JOST, 2008). No final de toda região paralela existe uma barreira implícita que faz com que as threads esperem até que todas as threads cheguem naquele ponto. A partir daí, apenas a thread inicial continua a execução do código. Entretanto o openMP especifica uma cláusula que pode ser usada para que o programador decida sobre a existência dessa barreira (CHAPMAN e JOST, 2008). O trecho de código da figura 9 realiza a soma de três vetores. Um construtor paralelo foi colocado no início do bloco, que indica ao compilador a existência de uma região paralela.


35

Figura 9: Código para realizar a soma de três vetores.

#pragma omp parallel { for (i = 0; i < n; i++) v[i] = a[i]+b[i]+c[i]; // Efetua a soma dos 3 vetores a,b,c e atribui o valor ao vetor v }

Este construtor indica que todas as threads deste grupo irão executar as mesmas instruções, ou seja, sem a divisão de tarefas. As cláusulas inseridas na diretiva definem como será o compartilhamento de memória e o número de threads que executarão paralelamente o código. Dentre as principais clausulas estão: “private”, “shared” e “num_threads” (CHAPMAN e JOST, 2008).

Private (<nome das variáveis>)

A cláusula private define quais variáveis já declaradas na região serial do programa serão redefinidas para cada thread na região paralela. Quando uma variável é definida como privada, cada thread cria uma copia da mesma e conserva o seu valor.

Shared (<nome das variáveis>)

A cláusula shared define quais variáveis declaradas serão compartilhadas entre todas as threads na região paralela. Se nenhuma cláusula for especificada, todas as variáveis serão consideradas do tipo shared. É importante ressaltar que toda variável criada dinamicamente é do tipo shared.


36

Num_threads (<nome da variáveis inteira ou número inteiro>) Esta cláusula define em quantas threads a região paralela será dividida. É

importante ressaltar que mesmo definindo uma variável de ambiente fora do programa com o número de threads (export omp_num_threads = 8), serão obedecidas as definições existentes dentro do programa (CHAPMAN e JOST, 2008).

2.4.4.4 Diretiva For

A diretiva for especifica que o laço for declarado diretamente abaixo desta será executado em paralelo pelas threads definidas pela diretiva parallel. Esta diretiva deve necessariamente ser declarada dentro da região paralela. A estrutura de utilização da diretiva em C/C++ é mostrada na figura 10.

Figura 10: Código para soma de vetores.

#pragma omp parallel{ #pragma omp for for (i = 0; i < n; i++){ v[i] = a[i]+b[i]+c[i]; printf(“Thread %d executa a iteração %d do loop\n”,omp_get_thread_num(),i); }}

Como um construtor de compartilhamento de trabalho foi inserido no código, as iterações do laço serão divididas entre as threads, de forma que cada uma irá calcular alguns termos do vetor v. O padrão é dividir as iterações do laço igualmente e de forma ordenada entre as threads. Por exemplo, se existirem 20 iterações e 4 threads, todas ficarão com 5 iterações. Caso a divisão do número de iterações pelo número de threads não seja exata, o resto da divisão é distribuído igualmente entre as threads. Por exemplo, se existirem 19 iterações e 4 threads, três threads ficarão


37

com 5 iterações e uma thread ficará com as últimas 4 iterações. Porém, pode-se alterar a forma como as iterações são distribuídas entre as threads por meio da cláusula schedule (esta cláusula será definida seguir). Alguma das cláusulas de entrada para a diretiva for são: “shedule”, “ordered” e “nowait ”. Dentre as demais cláusulas existentes estas são as mais relevantes. Estas cláusulas terão a função de definir como as iterações serão distribuídas entre as threads (CHAPMAN e JOST, 2008).

Shedule (<tipo de divisão>,<número de divisões>)

A cláusula shedule define em quantas partes serão divididas as iterações e como esta divisão será feita. Os tipos de divisão mais relevantes para o desenvolvimento deste trabalho serão: “static” e “Dynamic”. Em ambos os modos as iterações são divididas em varias partes de acordo com o número especificado no segundo argumento da diretiva. No modo static, as iterações são divididas especificamente entre as threads. Cada uma tem quantidade fixa de iterações pra resolver. No modo dynamic, as iterações são alocadas dinamicamente para a thread que estiver livre no momento (CHAPMAN e JOST, 2008). Figura 11: Soma de vetores com a utilização da cláusula dynamic.

#pragma omp parallel{ n = 10; #pragma omp for schedule(dynamic,4) for (i = 0; i < n; i++){ v[i] = a[i]+b[i]+c[i]; printf(“Thread %d executa a iteração %d do loop\n”,omp_get_thread_num(),i); }}

De acordo com a figura 11, cada thread será executada em bloco de quatro iterações e em seguida solicitará mais um bloco de iterações, até que todas as iterações já tenham sido executadas.


38

Ordered

A cláusula ordered especifica que as iterações deverão ser executadas na mesma ordem que seriam no modo seqüencial. Esta cláusula não pode ser utilizada junto com a cláusula Schedule. Não há argumentos para esta cláusula. A figura 12 ilustra a utilização do construtor for, porém com uma simples modificação e a figura 13 mostra o resultado. Antes da função para printf foi inserido um construtor ordered para que as iterações sejam impressas na ordem seqüencial (CHAPMAN e JOST, 2008). Figura 12: Exemplo do construtor ordered.

#pragma omp parallel { #pragma omp for ordered for (i = 0; i < n; i++) { v[i] = a[i]+b[i]+c[i]; #pragma omp ordered printf(“Thread %d executa a iteração %d do loop\n”,omp_get_thread_num(),i); } }


39

Figura 13: Resultado do processo após a utilização da cláusula Ordered.

Thread 0 executa a iteração 0 Thread 0 executa a iteração 1 Thread 0 executa a iteração 2 Thread 2 executa a iteração 3 Thread 2 executa a iteração 4 Thread 1 executa a iteração 5 Thread 1 executa a iteração 6 Thread 3 executa a iteração 7 Thread 3 executa a iteração 8

Nowait

Permite

ajustar

o

desempenho

do

programa.

Quando

introduzir

o

compartilhamento de tarefas, é inserida uma barreira implícita no final. Quando os segmentos chegar ao fim da construção, avançam imediatamente para realizar outras tarefas. Observe, porém, que a barreira no final de uma região em paralelo não poderá ser suprimida (CHAPMAN e JOST, 2008). A figura 14 mostra o exemplo do uso da cláusula Nowait, que está sendo utilizada para suprimir a barreira implícita no final do construtor, pois não há dependência entre os dois construtores. Figura 14: Exemplo da cláusula Nowait.

#pragma omp parallel { #pragma omp for nowait for (i = 0; i < n; i++) v[i] = a[i]+b[i]+c[i]; #pragma omp for nowait for (i = 0; i < n; i++) d[i] = a[i]*b[i]; }


40

Cláusula Num_threads

De acordo com a figura 15, esta cláusula é utilizada para informar a quantidade de threads que serão executadas dentro de uma região paralela. Figura 15: Exemplo do código que define 4 threads que serão executada na região paralela.

omp_set_num_threads(4); // Define a qtde de thread #pragma omp parallel private(tid) shared(n) { tid = omp_get_thread_num(); #pragma omp single { printf("Valor de n = %d\n",n); printf("Número de threads na região paralela: %d\n",omp_get_num_threads()); } printf("Número da thread: %d\n",tid); } /*-- Fim da região paralela --*/ 2.4.4.5 Diretiva Sections

O construtor sections é utilizado para dividir tarefas entre as threads em blocos de código que não possui iterações. Sendo assim, cada thread irá executar um bloco de código diferente. Figura 16: Sintaxe do construtor sections.

#pragma omp sections[cláusula,...] novalinha { #pragma omp section novalinha instrução #pragma omp section novalinha instrução }


41

Também será necessário incluir no código da diretiva #pragma

omp

section, que indica qual a instrução que cada thread irá executar. Quando houver mais blocos de código do que threads, algumas threads irão executar mais de um bloco. Por outro lado, se houver mais threads do que tarefas a serem executadas, apenas algumas threads irão trabalhar e as outras ficarão ociosas. Se houver apenas uma thread na região paralela, ela vai executar todas as tarefas de forma seqüencial (CHAPMAN e JOST, 2008). Figura 17: O exemplo do construtor sections.

#pragma omp parallel { #pragma omp sections { #pragma omp section sum_vector (a,b,c,v,n); #pragma omp section subt_vector (a,b,d,v,n); } } . . . // função para soma de vetores void

sum_vector(double

double *v,

*a,

double

*b,

double

*c,

*b,

double

*d,

int n)

{ for (int i = 0; i < n; i++) v[i] = a[i]+b[i]+c[i]; } // função para subtração de vetores void

subt_vector(double

*a,

double

double *v, int n) { for (int i = 0; i < n; i++) v[i] = a[i]-b[i]-d[i]; }


42

De acordo com o exemplo acima da figura 17 do construtor sections, o código será executado por duas threads, uma delas irá executar a função

sum_vetores e a outra irá executar a função subt_vetores. As duas funções serão executadas simultaneamente, exceto se existir apenas uma thread.

2.4.4.6 Diretiva Single

Esse construtor indica que o código dessa diretiva deve ser executado apenas por uma thread, não necessariamente a thread principal, mas a primeira thread que atingir esse ponto de execução. A figura 19 ilustra o uso da diretiva. As outras threads esperam numa barreira implícita, no final do construtor

single, até que a thread que encontrou o construtor termine a execução. Figura 18: Sintaxe da diretiva single

#pragma omp single [cláusula,...] instrução ... #pragma omp single [cláusula,...] instrução ... Figura 19: Exemplo do construtor single.

#pragma omp parallel { #pragma omp single printf(“Inicio da região paralela … número de threads = %d\n”, omp_get_num_threads()); #pragma omp for for (i = 0; i < n; i++) { v[i] = a[i]+b[i]+c[i]; printf(“Thread %d executa a iteracao %d do loop\n”,omp_get_thread_num(),i); } }


43

No código acima, o construtor single foi utilizado para que apenas uma thread imprimisse na tela algumas informações da execução do código.

2.4.4.7 Diretivas de sincronização

No modelo de memória do OpenMP, uma variável pode ser do tipo

private (privada) ou do tipo shared (compartilhada). Variáveis compartilhadas são visíveis por todas as threads que executam o código, o acesso as mesmas deve acontecer de maneira organizada e sincronizada a fim de evitar as chamadas “condições de corrida” (race conditions). Condições de corrida são situações que acontecem quando duas ou mais threads tentam atualizar, ao mesmo tempo, uma mesma variável ou quando uma thread atualiza uma variável e outra thread acessa o valor dessa variável ao mesmo tempo. Quando uma condição de corrida acontece, o resultado vai depender da ordem de execução das threads e não há nenhuma garantia que a variável seja atualiza com o valor correto. Dessa forma, as diretivas de sincronização garantem que o acesso ou atualização de uma determinada variável compartilhada aconteça no momento certo (CHAPMAN e JOST, 2008).

Critical

Este construtor restringe a execução de uma determinada tarefa a apenas uma thread por vez. É utilizado para evitar condições de corrida, ou seja, que uma mesma variável compartilhada seja atualizada por mais de uma thread ao mesmo tempo. Figura 20: Sintaxe do construtor critical

#pragma omp critical [(nome)] novalinha Instrução


44

Conforme a figura 21, quando uma thread encontra uma sessão crítica, o processo fica aguardando até que nenhuma thread esteja executando as instruções da região critica. Quando nenhuma thread estiver executando essa sessão crítica, a thread que estava esperando entra na região e executa as instruções. Figura 21: – Funcionamento de uma região crítica

Fonte: Programa Campus Ambassador (2008)

De acordo com a figura 22, cada thread calcula uma parcela do produto escalar (ax). Em seguida a variável dx é atualizada com os valores calculados por cada uma das threads. O construtor critical é utilizado para impedir que mais de uma thread tente atualizar a variável dx ao mesmo tempo. Figura 22: Exemplo da utilização da diretiva critical.

#pragma omp parallel{ ax =0; #pragma omp for for (i = 0; i < n; i++){ ax += a[i]*b[i]; } #pragma omp critical dx += ax; }


45

Barrier

Esta diretiva é utilizada para sincronizar todas as threads em um determinado ponto do código. Quando um processo de thread encontra uma barreira, o mesmo fica esperando até que todas as threads cheguem naquele ponto e, a partir daí, elas continuam a execução do código ao mesmo tempo, conforme é ilustrado na figura 23. Figura 23: – Esquema ilustrativo de uma barreira

Fonte: Programa Campus Ambassador (2008)

Não é necessário colocar barreiras em todos os pontos do código, pois já existem barreiras implícitas definidas, como, por exemplo, no final dos construtores

parallel, for, sections e single. Conforme a figura 24 é necessária a utilização de barreiras para evitar condições de corrida, pois ela pode impedir que uma thread acesse uma variável antes que a mesma seja atualizada. Figura 24: Exemplo da sincronização com barreiras.

#pragma omp parallel private(id){ id = omp_get_thread_num(); if(id<omp_get_num_threads()/2) system(“sleep 3”); print_time(id,”antes”); #pragma omp barrier print_time(id,”depois”); }


46

3 SISTEMAS LINEARES

Muitos problemas de matemática numérica umérica são modelados em termos de um Sistema de Equações Lineares Algébricas (SELA). Isso vale em geral para o tratamento numérico de equações funcionais lineares que ocorrem, entre outras, como equações diferenciais parciais ou ordinárias e equações integrais que surgem em diversos problemas da Física e Engenharia. Temos, Temos, por exemplo, que a análise tensorial de uma estrutura elétrica complicada ou a análise de vibrações de um sistema mecânico requerem a resolução de um sistema de equações lineares algébricas (HIROFUME, 2009). 200 Definição Sistemas Lineares podem ser vistos como um conjunto de m equações e n incógnitas. De acordo com a figura 25, para ara representar a posição da linha e coluna ao qual um elemento pertence, pertence são usados os índices i e j respectivamente.

Figura 25: Sistema Linear Original

l Fonte: Hirofume (2009)

Onde:

- aij representa os coeficientes das equações, equações sendo i representando a linha que varia de 1 até n e j representando a coluna que varia de 1 até n;

- bi representa os termos independentes, independentes sendo i representando a linha que varia de 1 até n;

- xj representa as incógnitas, incógnitas sendo j representando a coluna que varia de 1 até n;


47

Os métodos para resolução de sistemas lineares podem ser divididos em dois grupos: 1. Métodos Exatos: São aqueles que geram a solução exata do sistema com um

número

finito

de

operações.

A

solução

apresenta

erros

de

arredondamento, que são acumulativos ao longo da resolução. Para sistemas lineares de grande porte, o erro de arredondamento pode tirar o significado do resultado, isto é, a solução do sistema pode se afastar drasticamente do resultado real. Os métodos exatos são muito utilizados em problemas que contêm matrizes densas com dimensões pequenas. Exemplos: Método de Eliminação de Gauss, Método de Gauss-Compacto, Método de Eliminação de Gauss com Pivotamento Parcial, dentre outros (PRETTI, 2010). 2.

Métodos Iterativos: São aqueles que geram a solução do sistema com uma precisão previamente estabelecida através de um processo infinito convergente. Este tipo de método é eficaz em problemas que apresentam matrizes esparsas e de grandes dimensões. Os algoritmos desses métodos apresentam uma economia na utilização de memória do computador. Uma grande vantagem é a auto correção dos erros cometidos. Em algumas condições podem ser utilizados na resolução de sistemas não lineares. Os métodos iterativos podem ser divididos em: (a) Processos Estacionários: Quando cada aproximação é obtida da iteração anterior sempre pelo mesmo processo, isto é, a cada nova iteração a matriz de iteração não é alterada. Exemplos: Método de Jacobi-Richardson e Método de Gauss-Seidel; (b) Processos Não-Estacionários: A matriz de iteração não é constante. A cada iteração é obtida uma aproximação seguindo as restrições e utilizando as informações das iterações anteriores. O objetivo é buscar a melhor aproximação a cada iteração. São muito eficientes, tanto numericamente quanto computacionalmente, na solução de sistemas esparsos de grandes dimensões (PRETTI, 2010). Devido às características computacionais dos métodos iterativos citados,

estes são muito utilizados na resolução de sistemas de grande porte na computação. Por este motivo utilizar implementações paralelas nestes métodos


48

Iterativos, pode possibilitar uma solução mais rápida, pois divide a carga de trabalho entre o maior número de processos.

3.1 Produto Matriz

É uma forma muito frequente e vantajosa de representação de sistemas lineares é a notação de forma matricial:

Ax = b Onde: - A representa a matriz de coeficientes; - x representa o vetor de incógnitas; - b representa o vetor de termos independentes; Conforme a figura 26, o sistema linear é representado da seguinte forma:

Figura 26: - Produto Matriz

a11

a12 ... a1n

x1

b1

a21

a22 ... a2n

x2

b2

...

...

xn

bm

am1

...

...

am2 ... amn

A obtenção do vetor b da expressão é feito através do cálculo do Produto Matriz Vetor entre a matriz A e o vetor x. Analisando essa operação em partes é possível afirmar que cada k é calculado pelo produto interno da linha k da matriz A, que pode ser vista como um vetor, e o vetor b.

R = ∑

R = (a1 * b1)+(a2 * b2)+...+(an * bm) ai * bi


49

3.1.1 Algoritmo Seqüencial De acordo com a figura 27, a implementação do algoritmo para multiplicação de duas matrizes, sem o uso de paralelismo.

Figura 27: - O algoritmo do Produto Matriz Vetor.

//** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor

: Gilmar Prado Paiva

//** Data

: 10/10/2013

#define NRA 8 // número de linhas da matriz A #define NCB 8 // numero de columnas da matrix B #define NCA 8 // número de colunas da matrix C int main (int argc, char *argv[]) { int i, j, k; double **a, **b, **c; double **res; for (i=0; i<NRA; i++) { for (j=0; j<NCB; j++) { res[i][j] = 0.0; for (k=0; k<NCA; k++) res[i][j] += a[i][k]*b[k][j]; printf("Resultado: %5.1f\n ", res[i][j]); } } }


50

3.1.2 Algoritmo Paralelo em OpenMp A multiplicação de matrizes pode ser paralelizada por linha, coluna ou por elemento, mas é necessário fazer uma análise no código e identificar a melhor forma para que não tenha perda de desempenho da aplicação. Conforme a figura 28, foi inserida a diretiva #pragma omp for.

Este construtor é responsável pela

paralelização das linhas, distribuindo-as igualmente entre as threads do grupo. Figura 28: - Algoritmo com as implementações das diretivas do OpenMP.

//** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor

: Gilmar Prado Paiva

//** Data

: 10/10/2013

int A[NRA][NCA] = { {1, 2,8,6}, {3, 4,7,2} }; int B[NRA][NCA] = { {5, 6,9,1}, {7, 8,5,4} }; int C[NRA][NCA]; // Cria região paralela #pragma omp parallel{ #pragma omp for // Apenas as linhas foram paralelizadas for (i=0; i<NRA; i++) { for(j=0; j<NCB; j++) { for (k=0; k<NCA; k++) c[i][j] += a[i][k] * b[k][j]; } } }

Ao encontrar a diretiva parallel for, o OpenMP compartilha todas as variáveis que já estão naquele escopo. É normal compartilhar as variáveis A, B e C que contém os valores das matrizes. Porém, os índices dos loops for não podem ser compartilhados. O resultado poderia ser inesperado. Uma thread interferindo no índice do loop de outra thread causando um problema grave de sincronização. Para evitar que isso ocorra, conforme a figura 29, usa-se a diretiva de sincronização


51

private. As variáveis marcadas como private não são compartilhadas entre threads. E de acordo com a figura 29, existe uma diretiva de sincronização shared que servirá para explicitar que uma variável deve ser compartilhada entre as threads. Figura 29: - O algoritmo do Produto Matriz Vetor em OpenMP e com as devidas correções.

//** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor

: Gilmar Prado Paiva

//** Data

: 10/10/2013

int A[NRA][NCA] = { {1, 2,8,6}, {3, 4,7,2} }; int B[NRA][NCA] = { {5, 6,9,1}, {7, 8,5,4} }; int C[NRA][NCA]; // Cria região paralela #pragma omp parallel shared(a,b,c) private(i,j,k){ #pragma omp for for (i=0; i<NRA; i++) { for(j=0; j<NCB; j++) { for (k=0; k<NCA; k++) { c[i][j] += a[i][k] * b[k][j]; } } }


52

3.2 Método Iterativo de Jacobi Este método iterativo, iterativo busca encontrar a solução de um sistema Ax=b, onde

A e uma matriz de coeficientes, ficientes, x é um vetor de incógnitas a ser determinado e b e o vetor de termos independentes. Através Atrav de sucessivas aproximações aproxima iterativas o método consegue atingir uma solução solu aproximada da exata. ata. No entanto, este processo pode ser bastante demorado, dependendo do tamanho do sistema a ser resolvido. Uma solução ção para melhorar o seu desempenho, é a utilização de implementações paralelas, dividindo a carga de trabalho entre um numero maior de processos [Keys, 2000]. O método tem em a vantagem de ser mais simples de se implementar no computador do que o método de escalonamento, scalonamento, por existir uma menor possibilidade ao acúmulo de erros de arredondamento. De acordo com a figura 30, este método produz uma seqüência de soluções x1,x2,...xk,..., que aproximam a solução do sistema a partir de uma solução aproximada inicial X0.

1º Passo: Dado o sistema da figura 25, temos que isolar x1 na equação 1, e x2 na equação 2, e assim sucessivamente. Figura 30: - Sistema linear após transformações

Fonte: Hirofume (2009)


53

2º Passo: 0

0

[0 0 ... 0]T

Escolher a solução Inicial X , geralmente X =

3º Passo: Utilizar as equações do 1º Passo para gerar a sequencia de soluções até que o critério de parada seja atingido k

Ao utilizar as equações, só atualizamos a solução atual x ao final de cada passo.

Critérios de parada -

Solução “estabilizada”

Max (|X

k+1

- X

k

|) <

ε,

onde

ε

representa a tolerância de erro; - Número máximo de iterações N.

3.2.1 Algoritmo Estruturado do Método Iterativo de Jacobi

A figura 31 mostra a entrada: matriz de coeficientes A, vetor de termos independentes b, solução inicial X0, tolerância

ε e número máximo de iterações.


54

Figura 31: - Algoritmo Estruturado do Método Iterativo de Jacobi

//** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor

: Gilmar Prado Paiva

//** Data

: 10/10/2013

[L,c] = tamanho(A); para i=1 ate L b(i) = b(i)/A(i,i); para j= 1 ate L se i ~= j C(i,j) = -A(i,j)/A(i,i); senao C(i,j) = 0; fim_se fim_para cont = 0; xa = xn = x0; faça xa = xn; xn = b + C*xa; cont = cont +1; fim para Enquanto max(vabsoluto(xn-xa)) >

ε

e cont < N

saída(“a solução do sistema é “ x);

Algoritmo de Jacobi possui vários laços for que podem ser paralelizados, mas no exemplo que foi trabalhado, utilizou-se um número alto de equações (200 equações), e, conforme a figura 32, foi paralelizada apenas o laço que controla o número de iterações. Para 100 iterações utilizando o Processador Intel® Coretm 2 Duo, foram criadas 2 threads automaticamente e os processos divididos entre elas, pois o construtor #pragma omp for, é encarregado de fazer esta divisão.


55

Figura 32: - Algoritmo de Jacobi com OpenMP

//** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor

: Gilmar Prado Paiva

//** Data

: 25/10/2013

#pragma omp parallel // Cria a região paralela { #pragma omp master // excecuta apenasa thread principal printf("\nTotal de Threads %d \n", omp_get_num_threads()); // Paraleliza o laço for que define o nº de iterações // Nowait – Indica a não utilização de barreiras #pragma omp for nowait private(j,i) reduction (+:soma) for (k=1;k<=niTeracao;k++){ for (i=0; i<nEquacao; i++) xv[i] =(double) x[i]; for (i=0;i<nEquacao;i++){ soma=0; for (j=0;j<nEquacao;j++) // somatario if (j != i) soma=soma+(matriz[i][j]*xv[j]); x[i] = (xb[i] - soma) / matriz[i][i]; } for (i=0; i<nEquacao; i++){ if (abs(xv[i] - x[i]) > tolerancia){ printf("\nNao atingiu a precisão!"); break; } } }// Fim do laço com nº de Iterações // Fim da Regiao Paralela


56

3.3 Método Iterativo de Gaus-Seidel A diferença entre o método Jacobi e o de Gaus-Seidel, é que este último utiliza para calcular o valor de uma incógnita em uma determinada iteração, os valores das outras incógnitas já calculadas nesta mesma iteração. Em conseqüência disto, a convergência é mais rápida (DIEGUEZ, 1992). A figura 33 ilustra a fórmula de recorrência utilizada neste método: Figura 33: – Formula de Recorrência de Gauss-Seidel

Fonte: Dieguez (1992).

Os critérios de parada e as condições de convergência para este método são os mesmos que foram apresentados para o método de Jacobi. A figura 34 ilustra o código seqüencial do método e a figura 35 ilustra os pontos que foram paralelizados.


57

3.3.1 Algoritmo Estruturado do Método Iterativo de Gauss-Seidel Figura 34: – Algoritmo Estruturado do Método Iterativo de Gauss-Seidel

//** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor

: Gilmar Prado Paiva

//** Data

: 10/10/2013

Inicio Ler Tolerancia,NMI // Ler a tolerância de erro Ler X0 // Valores Inciais Para I de 1 até N executar X(I)<- X0 Fim (I) D <- 1 K <- 0 Enquanto D > 0 e K <= NMI executar D <- 0 Para I de 1 até N Executar XV <- X(I) SOMA <- 0 Para J de 1 até N executar Se J != I então SOMA <- SOMA + A(I,J) * X(J) Fim do (Se) Fim (J) X(I) <- (B(I) - SOMA)/A(I,I) Se |X(I)-XV| > Tolerância então D <- 1 Fim do I K <- K + 1 Fim do Enquanto Se k > NMI então Escreve “Não atingiu a precisão” Senão Escreve X(I), I variando de 1 até N Fim (Se) Fim


58

Figura 35: - Algoritmo de Gaus-Seidel com OpenMP

//** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor

: Gilmar Prado Paiva

//** Data

: 25/10/2013

#pragma omp parallel // Cria a região paralela { #pragma omp master // a linha será excecutada apenas na thread principal printf("\nTotal

de

Threads

%d

\n",

omp_get_num_threads()); // Paraleliza o laço for que define o nº de iterações // Nowait – Indica a não utilização de barreiras #pragma omp for nowait private(j,i) reduction (+:soma) for (k=1;k<=niTeracao;k++) { for (i=0;i<nEquacao;i++) { xv[i] =(double) x[i]; soma=0; for (j=0;j<nEquacao;j++) // somatario if (j != i) soma=soma+(matriz[i][j]*xv[j]); x[i] = (xb[i] - soma) / matriz[i][i]; } for (i=0; i<nEquacao; i++) { if (abs(xv[i] - x[i]) > tolerancia){ printf("\nNao atingiu a precisão!"); break; } } }// Fim do laço com nº de Iterações } // Fim da Regiao Paralela


59

4 RESOLUÇÃO DE SISTEMAS LINEARES UTILIZANDO OPENMP

A utilização do OpenMP assim como outras bibliotecas (PTHREADS, MPI, GPU), permitem a abertura de novas possibilidades de paralelização de operações matemáticas complexas. Estudiosos que trabalham com aplicações que tem como base problemas que envolvem equações diferenciais e álgebra linear, devem explorar a utilização deste processos na paralelização de partes críticas, isto é, nas áreas do programa que consomem mais tempo de processamento (CANAL DÚBITA, 2013). É possível perceber que, aplicações baseadas em Equações Diferenciais e Álgebra Linear apresentam como base operacional, os Sistemas Lineares e manipulação de Matrizes e Vetores. Com isso, neste trabalho foi criada uma aplicação como mostra a figura 36, para medir o desempenho da multiplicação de matrizes e resolução dos algoritmos de Jacobi e Gaus-Seidel. Os mesmos foram implementados de forma seqüencial em seguida foi feita uma paralelização de determinadas partes do algoritmo para melhorar o seu desempenho. Figura 36: – Aplicação para medir o desempenho da multiplicação de Matrizes e resolução de sistemas lineares.

Fonte:Autoria Própria (2013)


60

4.1 Ambientes de Testes

Toda a aplicação foi desenvolvida na linguagem C e compilada utilizando o compilador GCC da GN. Foi utilizado o parâmetro â&#x20AC;&#x201C;fopenmp para permitir a compilação das diretivas OpenMP. Para obter um ganho maior no desempenho, trabalhou-se dentro do ambiente Linux e foi utilizado dois computadores multicore sendo um processador IntelÂŽ Coretm 2 Duo 4 GB de memĂłria de RAM 800Mhz DDR2a Intel Coretm 2 Duo e outra mĂĄquina Processador IntelÂŽ Coretm I5 4GB de memĂłria RAM 2,5 GHz. Foram feitos cinco testes para cada mĂŠtodo, e, diante dos resultados obtidos, foi calculado a mĂŠdia aritmĂŠtica e o desvio padrĂŁo dos tempos(s) apresentados ao final de cada execução. Tabela 1: MĂŠtodo Iterativo de Jacobi â&#x20AC;&#x201C; Dez Iteraçþes â&#x20AC;&#x201C; Cinco vezes consecutivas.

NÂş

Tempo de execução

Tempo de execução

SeqĂźencial (s)

Paralelo (s)

1

0,09

0,05

2

0,10

0,05

3

0,10

0,04

4

0,10

0,045

5

0,10

0,05

Aplicando a mÊdia aritmÊtica para a equação do tempo seqßencial abaixo teremos:

Tserial =

,  ,  ,  ,  , 

Calculando o desvio padrĂŁo da mĂŠdia teremos:

= 0,098s


61 ,

" = #$ -

(&' − &)* +−1

" = 0,00002s

Aplicando a média aritmética para a equação do tempo paralelo abaixo teremos:

Tparalelo =

,  ,  , / , / ,  

= 0,047s

Calculando o desvio padrão da média teremos:

(&' − &)* " = #$ +−1 ,

-

" = 0,04624s

4.1.1 Análise do Desempenho

Nesta seção serão realizadas as análises em gráficos e tabelas do desempenho das implementações seqüencial e paralelo dos algoritmos testados. Os tempos de execução obtidos são referentes apenas a execução da função do cálculo das matrizes e vetores, sendo desprezados os tempos de leitura dos dados e alocação das variáveis do problema. Para medir o tempo de cada processo, foi utilizada a função omp_get_wtime() que retorna um valor em precisão dupla igual ao tempo decorrido em segundos. No código abaixo, está ilustrado como a função deve ser utilizada.


62

Figura 37: – Algoritmo para cálculo do tempo

#include <omp.h> double TempoInicial; double TempoFinal; TempoInicial = omp_get_wtime(); ...

processando ...

TempoFinal = omp_get_wtime(); printf(“Tempo Gasto %f seg.\n”, TempoFinal - TempoInicial);

De acordo com a figura 38, os tempos obtidos foram medidos em segundos para que não houvesse perda na demonstração dos resultados. Figura 38: – Aplicação em funcionamento testando um método interativo

Fonte:Autoria Própria (2012)


63

tm

Tabela 2: Multiplicação de matrizes - Processador Intel® Core 2 Duo 4 GB de memória de RAM 800Mhz DDR2.

Dimensão da Matriz 100 x 100 200 x 200 300 x 300 400 x 400 500 x 500 600 x 600 700 x 700 800 x 800 900 x 900 1000 x 1000

Seqüencial (s) Seqüencial(s) Média (5 Desvio Execuções) Padrão 0,02 0,01 0,16 0,01 0,56 0,01 1,73 0,03 3,82 0,01 7,52 0,03 12,01 0,01 15,76 0,01 25,73 0,03 35,62 0,03

Paralelo (s) Média (5 Execuções) 0,02 0,11 0,39 1,17 2,48 4,24 7,66 9,42 16,57 22,87

Paralelo (s) Desvio Padrão 0,00 0,01 0,01 0,06 0,09 0,19 0,28 0,47 0,70 0,04

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013) tm

Gráfico 1: Desempenho da multiplicação de matrizes - processador Intel® Core

2 Duo 4 GB de

memória de RAM 800Mhz DDR2

Tempo de Processamento (s)

40 35 30 25 20 15

Sequencial

10

Paralelo

5 0 100 x 200 x 300 x 400 x 500 x 600 x 700 x 800 x 900 x 1000 x 100 200 300 400 500 600 700 800 900 1000 Dimensão da Matriz

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

A realização dos cálculos da multiplicação de matrizes quadradas, com o tamanho das matrizes variando entre 100 x 100 e 1000 x 1000. A aplicação foi testada seqüencialmente e em seguida usando as diretivas OpenMP. Os resultados podem ser vistos na Tabela 2 e no Gráfico 1.


64

tm

Tabela 3: Desempenho da multiplicação de matrizes - Processador Intel® Core I5 4GB de memória RAM 2,5 GHz.

Dimensão da Matriz 100 x 100 200 x 200 300 x 300 400 x 400 500 x 500 600 x 600 700 x 700 800 x 800 900 x 900 1000 x 1000

Seqüencial(s) Seqüencial(s) Média (5 Desvio Execuções) Padrão 0,01 0,00 0,07 0,00 0,23 0,01 0,76 0,06 1,00 0,02 2,39 0,04 4,49 0,04 6,63 0,03 9,77 0,03 12,33 0,05

Paralelo (s) Média (5 Execuções) 0,01 0,03 0,16 0,38 0,69 1,19 2,23 3,22 5,10 5,88

Paralelo (s) Desvio Padrao 0,00 0,00 0,01 0,01 0,01 0,03 0,06 0,08 0,05 0,12

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013) tm

Gráfico 2: Multiplicação de matrizes - Processador Intel® Core

I5 4GB de memória RAM 2,5 GHz.

Tempo de Processamento (s)

14 12 10 8 6

Sequencial

4

Paralelo

2 0 100 x 100

200 x 200

300 x 300

400 x 400

500 x 500

600 x 600

700 x 700

800 x 800

900 x 1000 x 900 1000

Dimensão da Matriz

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

A realização dos cálculos da multiplicação de matrizes quadradas, com o tamanho das matrizes variando entre 100 x 100 e 1000 x 1000. A aplicação foi testada seqüencialmente e em seguida usando as diretivas OpenMP. Os resultados podem ser vistos na Tabela 3 e no Gráfico 2.


65

Gráfico 3: Multiplicação de matrizes - Comparação de Desempenho Intel® Core tm

memória RAM 2,5 GHz x Intel® Core

tm

I5 4GB de

2 Duo 4 GB de memória de RAM 800Mhz DDR2.

Tempo de Processamento (s)

25 20 15 10 5 0 100 x 100

200 x 200

300 x 300

400 x 400

500 x 500

600 x 600

700 x 700

800 x 800

900 x 1000 x 900 1000

Intel® Coretm 2 Duo

0,02

0,11

0,39

1,17

2,48

4,24

7,66

9,42

16,57

22,87

Intel® Coretm I5

0,01

0,03

0,16

0,38

0,69

1,19

2,23

3,22

5,1

5,88

Fonte: Dados fornecidos pelos testes da aplicação OpenMP (2013)

De acordo com o gráfico 3, constata-se que houve um ganho de desempenho do processador Intel® Coretm I5 em relação ao processador Intel® Coretm 2 Duo.


66

Tabela 4: Método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - Processador tm

Intel® Core 2 Duo 4 GB de memória de RAM 800Mhz DDR2.

Nº de Iterações 5000 10000 15000 20000 25000 30000 35000 40000 45000 50000

Seqüencial(s) Seqüencial(s) Média (5 Desvio Execuções) Padrão 43,2131 0,0020 86,4296 0,0017 129,6450 0,0057 172,8651 0,0199 216,0940 0,0148 259,3045 0,0431 302,5852 0,1450 345,7379 0,0123 388,9474 0,0263 432,1969 0,0149

Paralelo (s) Média (5 Execuções) 21,9456 43,9212 85,7202 87,7202 109,7732 131,7160 156,7720 175,5927 197,3504 219,3090

Paralelo(s) Desvio Padrão 0,0018 0,0442 0,0761 0,0803 0,0143 0,0296 0,3909 0,1391 0,2604 0,3063

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013) Gráfico 4: Método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - Processador tm

Intel® Core 2 Duo 4 GB de memória de RAM 800Mhz DDR2. 500,0000 Tempo de Processamento (s)

450,0000 400,0000 350,0000 300,0000 250,0000 Sequencial

200,0000

Paralelo

150,0000 100,0000 50,0000 0

10000

20000

30000

40000

50000

60000

Nº de Iterações

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

Foram realizados os cálculos do método iterativo de Jacobi utilizando 200 equações, com o número de iterações variando entre 5000 e 50000. Houve um ganho substancial de desempenho quando o código seqüencial foi paralelizado. Os resultados podem ser vistos na Tabela 4 e no Gráfico 4.


67

Tabela 5: Método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - Processador tm

Intel® Core I5 4GB de memória RAM 2,5 GHz.

Nº de Iterações

Seqüencial(s) Seqüencial(s) Média (5 Desvio Execuções) Padrão 34,5705 0,3844 69,1968 0,7618 103,7160 0,7246 138,2921 0,6455 172,8752 1,7020 207,4436 1,5071 242,0682 0,3064 276,5903 1,6108 311,1579 1,4790 345,7575 1,3635

5000 10000 15000 20000 25000 30000 35000 40000 45000 50000

Paralelo (s) Média (5 Execuções) 10,7270 21,4687 32,1429 42,8776 53,6571 64,3828 76,6302 85,8297 96,4649 107,1982

Paralelo(s) Desvio Padrão 0,0742 0,2598 0,3411 0,3062 0,7212 0,3421 0,3254 0,4084 0,8247 0,8022

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013) Gráfico 5: Método iterativo de Jacobi utilizando 200 equações e tolerância de 0,5 - Processador tm

Intel® Core I5 4GB de memória RAM 2,5 GHz.

Tempo de Processamento (s)

400,0000 350,0000 300,0000 250,0000 200,0000 Sequencial 150,0000 Paralelo 100,0000 50,0000 0

10000

20000

30000

40000

50000

60000

Nº de Iterações

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

Foram realizados os cálculos do método iterativo de Jacobi utilizando 200 equações, com o número de iterações variando entre 5000 e 50000. Houve um ganho substancial de desempenho quando o código seqüencial foi paralelizado. Os resultados podem ser vistos na Tabela 5 e no Gráfico 5.


68

tm

Gráfico 6: Método iterativo de Jacobi - Comparação de Desempenho Intel® Core I5 4GB de tm

memória RAM 2,5 GHz x Intel® Core

2 Duo 4 GB de memória de RAM 800Mhz DDR2.

Tempo de Processamento (s)

250,0000

200,0000

150,0000

100,0000

50,0000

-

5000

10000

15000

20000

25000

30000

35000

40000

45000

50000

Intel® Coretm 2 Duo

21,9456

43,9212

65,7587

87,7202

109,7732

131,7160

156,7720

175,5927

197,3504

219,3090

Intel® Coretm I5

10,7270

21,4687

32,1429

42,8776

53,6571

64,3828

76,6302

85,8297

96,4649

107,1982

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

De acordo com o gráfico 6, constata-se que houve um ganho de desempenho nos processos paralizados do processador Intel® Coretm I5 em relação ao processador Intel® Coretm 2 Duo.


69

Tabela 6: Método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5 Processador Intel® Core

Nº de Iterações

tm

2 Duo 4 GB de memória de RAM 800Mhz DDR2.

Seqüencial(s) Seqüencial(s) Média (5 Desvio Execuções) Padrão 43,2060 0,0048 86,4169 0,0032 129,6218 0,0027 172,8630 0,0449 216,0292 0,0277 259,2114 0,0027 302,4100 0,0008 345,6183 0,0069 388,8263 0,0159 432,0385 0,0032

5000 10000 15000 20000 25000 30000 35000 40000 45000 50000

Paralelo(s) Média (5 Execuções) 21,9047 43,7639 65,7510 87,6535 109,4751 131,4483 153,3476 175,1185 196,9178 218,7532

Paralelo(s) Desvio Padrão 0,0028 0,0252 0,0423 0,0072 0,0613 0,0319 0,0258 0,1042 0,0707 0,0383

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013) Gráfico 7: Método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5 Processador Intel® Core

tm

-

2 Duo 4 GB de memória de RAM 800Mhz DDR2.

Tempo de Processamento (s)

400,0000 350,0000 300,0000 250,0000 200,0000 Sequencial

150,0000

Paralelo 100,0000 50,0000 0,0000 0

10000

20000

30000

40000

50000

60000

Nº de Iterações

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

Foram realizados os cálculos do método iterativo de Gauss-Seidel utilizando 200 equações, com o número de iterações variando entre 5000 e 50000. Houve um ganho substancial de desempenho quando o código seqüencial foi paralelizado. O desvio padrão ficou alto em relação a outros testes, pois no computador havia processos em execução. O tempo de execução foi menor que o método de Jacobi, mas é devido ao algoritmo de Gauss-Seidel ser mais rápido. Os resultados podem ser vistos na Tabela 6 e no Gráfico 7.


70

Tabela 7: Método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5

-

tm

Processador Intel® Core I5 4GB de memória RAM 2,5 GHz.

Nº de Nº Iterações

Seqüencial(s) Seqüencial(s) Média (5 Desvio Execuções) Padrão 34,6028 0,1971 69,2096 0,3401 103,4115 0,4045 138,4425 0,8099 173,0135 1,639 207,5972 0,3890 242,1941 1,5525 276,7988 0,5752 311,4032 0,7259 346,0110 1,9071

5000 10000 15000 20000 25000 30000 35000 40000 45000 50000

Paralelo(s) Média (5 Execuções) 10,5335 21,0452 31,6183 42,1508 52,6444 63,2109 73,7418 84,2110 94,6938 105,1940

Paralelo(s) Desvio Padrão 0,0856 0,1972 0,2700 0,4528 0,6135 0,8971 0,9700 0,9917 0,6548 1,3933

Fonte: Dados fornecidos pelos testes da aplicação OpenMP (2013) Gráfico 8 : Método iterativo de Gauss-Seidel utilizando 200 equações e tolerância de 0,5

-

tm

Processador Intel® Core I5 4GB de memória RAM 2,5 GHz.

Tempo de Processamento (s)

400,0000 350,0000 300,0000 250,0000 200,0000 Sequencial

150,0000

Paralelo 100,0000 50,0000 0,0000 0

10000

20000

30000

40000

50000

60000

Nº de Iterações

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

Foram realizados os cálculos do método iterativo de Gauss-Seidel utilizando 200 equações, com o número de iterações variando entre 5000 e 50000. Houve um ganho substancial de desempenho quando o código seqüencial foi paralelizado. O tempo de execução foi menor que o método de Jacobi, mas é devido ao algoritmo de Gauss-Seidel ser mais rápido. Os resultados podem ser vistos na Tabela 7 e no Gráfico 8.


71

tm

Gráfico 9: Método iterativo de Gauss-Seidel - Comparação de Desempenho Intel® Core

I5 4GB

tm

de memória RAM 2,5 GHz x Intel® Core 2 Duo 4 GB de memória de RAM 800Mhz DDR2.

Tempo de Processamento (s)

250

200

150

100

50

0

5000

10000

15000

20000

25000

30000

35000

40000

45000

50000

Intel® Coretm 2 Duo

21,9047

43,7639

65,7510

87,6535

109,4751

131,4483

153,3476

175,1185

196,9178

218,7532

Intel® Coretm I5

10,5335

21,0452

31,6183

42,1508

52,6444

63,2109

73,7418

84,211

94,6938

105,194

Fonte: Dados obtidos pelos testes da aplicação OpenMP (2013)

De acordo com o gráfico 9, constata-se que houve um ganho de desempenho do processador Intel® Coretm I5 em relação ao processador Intel® Coretm 2 Duo, e houve um desvio padrão baixo na amostragem dos dados.


72

5 CONCLUSÃO

Com base no presente trabalho, pode-se constatar que o paralelismo dos algoritmos na resolução de sistemas lineares, foi executado em menor tempo do que os algoritmos sem paralelismo, principalmente quando existe um número alto de equações numéricas. E também ficou constatado que, quando são utilizados processadores com maior número de core, o tempo de execução dos processos é ainda menor. A implementação do código foi desenvolvida na linguagem C dentro do ambiente do Linux, adotando o modelo de programação OpenMP. Este modelo possibilitou que a aplicação utilizasse todos os recursos dos processadores multicores. A principal dificuldade encontrada foi durante a coleta dos dados, pois, quando foi executada com um número alto de equações e com a existência de outras aplicações em uso, o desvio padrão ficou alto. Para diminuir o problema, foi necessária a execução da aplicação por várias vezes, para que o resultado não dispersasse em relação à média. Sendo assim, todas as análises desenvolvidas neste trabalho, mostram que a utilização da programação paralela quando aplicada de forma correta, pode-se obter um alto ganho de desempenho. E todos os métodos propostos obtiveram um resultado satisfatório e a hipótese levantada neste trabalho foi confirmada.

6.1 Sugestões para trabalhos futuros

Para complementar este trabalho, como foram realizados estudos utilizando o OpenMP para verificar o ganho de desempenho de programas paralelos na resolução de sistemas lineares, pode-se realizar um estudo para verificação de ganho de desempenho através de GPU’s da NVIDIA, que é utilizada para processamento de aplicações focadas em métodos numéricos com o uso da arquitetura CUDA utilizando a linguagem de programação CUDA+C, que mescla instruções próprias desta arquitetura com algumas diretivas da linguagem C.


73

REFERÊNCIAS

AKHTER, S. ; ROBERTS, J. Multi-Core Programming: Increasing Performance through Software Multi-threading. Intel Press, 2006. BONAT, A.; GIUSTSI, F., Analise de compiladores com suporte a OpenMP. Santa Cruz do Sul, 2008. Canal Dúbita – Programação Paralela OpenMP. www.youtube.com/user/dubitatv>. Acesso em: 02 Nov. 2013.

Disponível

em:

<

CESAR A, F. de Rose; PHILLIPE O. A. Navaux, Arquiteturas Paralelas. Sagra Luzzato, 2003.

CHAPMAN, B.; JOST, G.; PAS, R. van der. Using openMP: portable shared memory parallel programming. Cambridge, MA: MIT Press, 2008. (Scientific and Engineering Computation Series).

Controle de Granularidade Tarefas em OpenMP. <http://www.lume.ufrgs.br>. Acessado em 27 de Out. de 2013.

DIEGUEZ, 1992.

Disponivel

em:

J.P. do Prado, Métodos Numéricos Computacionais. Interciência,

HIROFUME, C. A.; COLLI, E., Cálculo Numérico - Fundamentos e Aplicações. Departamento de Matemática Aplicada , 2009.

Introdução ao Processamento Paralelo e de Alto Desempenho. Disponível em: <http://www.rc.unesp.br>. Acesso em: 15 Jul. 2013.

LLNL Lawrence Livermore National Laboratory. <https://computing.llnl.gov>. Acesso em: 30 Set. 2013.

Disponível

em:

OLIVEIRA, M. S., Controle de Granularidade de Tarefas em OpenMP. UFRGS, 2011.

OmpSCR: OpenMP Source Code Repository Web Site. Disponível <http://sourceforge.net/projects/ompscr/>. Acessado em 05 de Out. de 2013.

em:


74

OpenMP: Simple, Portable, Scalable SMP Programming. OpenMP. Disponível em: <http://www.openmp.org/>. Acessado em 15 de Ago de 2013. PATTERSON, D. A.; HENNESSY, J. L. Arquitetura de Computadores: uma abordagem quantitativa. 4ª ed. Rio de Janeiro: CAMPUS / ELSEVIER, 2005.

PRETTI, K. A.R.; FASSARELLA, L.J.V. Filho., Introdução ao Cuda Utilizando Métodos Numéricos. Vila Velha, 2010.

PRODANOV, C. C.; CESAR, E. de Freitas , metodologia do trabalho científico Métodos e Técnicas da Pesquisa e do Trabalho Acadêmico. Novo Hamburgo: Feevale, 2013.

Sistemas Lineares – Métodos Iterativos. Disponível em: /silvia>. Acessado em 15 de Out. de 2013.

< http://www.inf.ufpr.br

TANENBAUM, A. S. Sistemas Operacionais Modernos. 2a. ed. Pearson, 2003. ZULLIAN, L.G., Avaliação de Desempenho do OpenMP em Arquiteturas Paralelas. Porto Alegre, 2006


75

APÊNDICE A Código fonte de Resolução de Sistemas Lineares – Método Iterativo de Jacobi Sem Paralelização //** //** //** //** //**

Empresa: Fainor – Faculdade Ind. Do Nordeste Autor : Gilmar Prado Paiva Data : 10/10/2013 m_jacobi_sequencial.c Metodo Iterativo de jacobi sem paralelismo

#include #include #include # define

<stdio.h> <stdlib.h> <math.h> nEquacao 200

int main() { int niTeracao=0,nExecucao=0; int tid, nthreads, i, j, k,z; int nTotalExecucao=5; double double double double double double

starttime, stoptime; soma = 0; tserial[5]; tolerancia =0; tmediaserial=0; tdesviopadraoserial=0;

// Desempenho do Metodo Iterativo de Jacobi printf("\nDigite a Primeira Interacao: "); scanf(" %i", &niTeracao); printf("\nDigite o Valor da Tolerancia: "); scanf(" %d", &tolerancia); double matriz[nEquacao][nEquacao]; double xv[nEquacao],x[nEquacao], xb[nEquacao]; printf("\nIniciando o metodo iterativo de JACOBI\n"); printf("\nNo de Iteracoes %d \n",niTeracao); printf("\nNo de Equacoes %d \n",nEquacao); printf("\nNo de Execucoes %d \n",nTotalExecucao); printf("\n--------------------------------------------------------"); printf("\n\nAguarde! Gerando as Equacoes...\n\n"); // preenche a matriz for (i=0; i<nEquacao; i++) { xb[i]= (double) ((nEquacao+1)*i); x[i]=(double) 0.0; for (j=0; j<nEquacao; j++) matriz[i][j] = (double) ((i+j)*i); } for (z=1; z<=10; z++) { // Laço que define a qtde de execução for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { // ******** Inicio do Processo Serial ********** starttime = omp_get_wtime(); // capta o tempo inicial for (k=1; k<=niTeracao; k++) {


76

for (i=0; i<nEquacao; i++) xv[i] =(double) x[i]; for (i=0; i<nEquacao; i++) { soma=0; for (j=0; j<nEquacao; j++) // somatario if (j != i) soma=soma+(matriz[i][j]*xv[j]); x[i] = (xb[i] - soma) / matriz[i][i]; } for (i=0; i<nEquacao; i++) { if (abs(xv[i] - x[i]) > tolerancia) { break; } } } stoptime = omp_get_wtime(); // capta o tempo final tserial[nExecucao]= stoptime-starttime; } // fim nº Total de Execução //**************** Calcula Media Aritmetica e Desvio Padrao tmediaserial =0; // Calcula a media artimetica for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tmediaserial =tmediaserial+ tserial[nExecucao]; } tmediaserial = tmediaserial / nTotalExecucao; tdesviopadraoserial =0; // Calcula o desvio padrão for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tdesviopadraoserial =tdesviopadraoserial+ pow( (tserial[nExecucao] - tmediaserial),2); } tdesviopadraoserial =sqrt(tdesviopadraoserial /(nTotalExecucao-1)); // impressão dos tempos seriais e parelos if (z==1) { printf("\n+--------------------+-------------+------------+"); printf("\n| No de Iteracao |Sequencial(s)|D Padrao(ts)|"); printf("\n+--------------------+-------------+------------+"); } printf("\n| %d | %3.2f s | %3.2f s |", niTeracao, tmediaserial,tdesviopadraoserial); printf("\n+-----------------------+-------------+------------+"); niTeracao = niTeracao + 10; } // Fim da Qtde Iteracao return 0; }


77

APÊNDICE B Código fonte de Resolução de Sistemas Lineares – Método Iterativo de GaussSeidel Sem Paralelização //** //** //** //** //**

Empresa: Fainor – Faculdade Ind. Do Nordeste Autor : Gilmar Prado Paiva Data : 10/10/2013 m_gausseidel_sequencial.c Metodo Iterativo de Gauss-Seidel sem paralelismo

#include <stdio.h> #include <stdlib.h> #include <math.h> # define nEquacao 200 int main() { int niTeracao=0,nExecucao=0; int tid, nthreads, i, j, k,z; int nTotalExecucao=5; double starttime, stoptime; double soma = 0; double tserial[5]; double tolerancia =0; double tmediaserial=0; double tdesviopadraoserial=0; // Desempenho do Metodo Iterativo de Jacobi printf("\nDigite a Primeira Interacao: "); scanf(" %i", &niTeracao); printf("\nDigite o Valor da Tolerancia: "); scanf(" %d", &tolerancia); double matriz[nEquacao][nEquacao]; double xv[nEquacao],x[nEquacao], xb[nEquacao]; printf("\nIniciando o metodo iterativo de GAUSS-SEIDEL\n"); printf("\nNo de Iteracoes %d \n",niTeracao); printf("\nNo de Equacoes %d \n",nEquacao); printf("\nNo de Execucoes %d \n",nTotalExecucao); printf("\n---------------------------------------------------------"); printf("\n\nAguarde! Gerando as Equacoes...\n\n"); // preenche a matriz for (i=0; i<nEquacao; i++) { xb[i]= (double) ((nEquacao+1)*i); x[i]=(double) 0.0; for (j=0; j<nEquacao; j++) matriz[i][j] = (double) ((i+j)*i); } for (z=1; z<=10; z++) { // Laço que define a qtde de execução for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { // ******** Inicio do Processo Serial ********** starttime = omp_get_wtime(); // capta o tempo inicial for (k=1; k<=niTeracao; k++)


78

{ for (i=0; i<nEquacao; i++) { soma=0; xv[i] =(double) x[i]; for (j=0; j<nEquacao; j++) // somatario if (j != i) soma=soma+(matriz[i][j]*xv[j]); x[i] = (xb[i] - soma) / matriz[i][i]; } for (i=0; i<nEquacao; i++) { if (abs(xv[i] - x[i]) > tolerancia) { break; } } } stoptime = omp_get_wtime(); // capta o tempo final tserial[nExecucao]= stoptime-starttime; } // fim nº Total de Execução //********** Calcula Media Aritmetica e Desvio Padrao tmediaserial =0; // Calcula a media artimetica for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tmediaserial =tmediaserial+ tserial[nExecucao]; } tmediaserial = tmediaserial / nTotalExecucao; tdesviopadraoserial =0; // Calcula o desvio padrão for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tdesviopadraoserial =tdesviopadraoserial+ pow( (tserial[nExecucao] - tmediaserial),2); } tdesviopadraoserial =sqrt(tdesviopadraoserial /(nTotalExecucao-1)); // impressão dos tempos seriais e parelos if (z==1) { printf("\n+--------------------+-------------+------------+"); printf("\n| No de Iteracao |Sequencial(s)|D Padrao(ts)|"); printf("\n+--------------------+-------------+------------+"); } printf("\n| %d | %3.2f s | %3.2f s |", niTeracao, tmediaserial,tdesviopadraoserial); printf("\n+--------------------+-------------+------------+"); niTeracao = niTeracao + 10; } // Fim da Qtde Iteracao return 0; }


79

APÊNDICE C Código fonte de Resolução de Sistemas Lineares – Método Iterativo de Jacobi Com Paralelização OpenMP //** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor : Gilmar Prado Paiva //** Data : 10/10/2013 //** m_Jacobi_OpenMP.c //** Metodo Iterativo de Jacobi com paralelismo OpenMP #include <omp.h> #include <stdio.h> #include <stdlib.h> #include <math.h> # define nEquacao 200 int main() { int niTeracao=0,nExecucao=0; int i, j, k,z; int nTotalExecucao=5; double inicioRegiaoParalela,fimRegiaoParalela; double soma = 0; double tparalelo[5]; double tolerancia =0; double tmediaparalelo=0; double tdesviopadraoparalelo=0; // Desempenho do Metodo Iterativo de Jacobi printf("\nDigite a Primeira Interacao: "); scanf(" %i", &niTeracao); printf("\nDigite o Valor da Tolerancia: "); scanf(" %d", &tolerancia); double matriz[nEquacao][nEquacao]; double xv[nEquacao],x[nEquacao], xb[nEquacao]; printf("\nIniciando o metodo iterativo de JACOBI\n"); printf("\nNo de Iteracoes %d \n",niTeracao); printf("\nNo de Equacoes %d \n",nEquacao); printf("\nNo de Execucoes %d \n",nTotalExecucao); printf("\n---------------------------------------------------------"); printf("\n\nAguarde! Gerando as Equacoes...\n\n"); // preenche a matriz for (i=0; i<nEquacao; i++) { xb[i]= (double) ((nEquacao+1)*i); x[i]=(double) 0.0; for (j=0; j<nEquacao; j++) matriz[i][j] = (double) ((i+j)*i); } for (z=1; z<=10; z++) { // Laço que define a qtde de execução for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { // ********* Inicio do Processo paralelo ************** // capta o tempo inicial inicioRegiaoParalela = omp_get_wtime(); #pragma omp parallel // Cria a região paralela { #pragma omp for nowait private(j,i) reduction (+:soma) for (k=1; k<=niTeracao; k++)


80

{ for (i=0; i<nEquacao; i++) xv[i] =(double) x[i]; for (i=0; i<nEquacao; i++) { soma=0; for (j=0; j<nEquacao; j++) // somatario if (j != i) soma=soma+(matriz[i][j]*xv[j]); x[i] = (xb[i] - soma) / matriz[i][i]; } for (i=0; i<nEquacao; i++) { if (abs(xv[i] - x[i]) > tolerancia) { break; } } } } // Fim da Regiao Paralela // capta o tempo final da região paralela fimRegiaoParalela = omp_get_wtime(); tparalelo[nExecucao] = fimRegiaoParalela inicioRegiaoParalela; } // fim nº Total de Execução //**************** Calcula Media Aritmetica e Desvio Padrao tmediaparalelo =0; // Calcula a media artimetica for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tmediaparalelo =tmediaparalelo+ tparalelo[nExecucao]; } tmediaparalelo = tmediaparalelo/ nTotalExecucao; tdesviopadraoparalelo =0; // Calcula o desvio padrão for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tdesviopadraoparalelo =tdesviopadraoparalelo+ pow( (tparalelo[nExecucao] - tmediaparalelo),2); } tdesviopadraoparalelo=sqrt(tdesviopadraoparalelo/(nTotalExecucao1)); // impressão dos tempos seriais e parelos if (z==1) { printf("\n+--------------------+------------+------------+"); printf("\n| No de Iteracao |Paralela(s) | D Padrao(s)|"); printf("\n+--------------------+------------+------------+"); } printf("\n| %d | % 3.2f s | % 3.2f s |", niTeracao, tmediaparalelo,tdesviopadraoparalelo); printf("\n+--------------------+------------+------------+"); niTeracao = niTeracao + 10; } // Fim da Qtde Iteracao return 0; }


81

APÊNDICE D Código fonte de Resolução de Sistemas Lineares – Método Iterativo de GaussSeidel Com Paralelização OpenMP //** Empresa: Fainor – Faculdade Ind. Do Nordeste //** Autor : Gilmar Prado Paiva //** Data : 10/10/2013 //** m_gausseidel_OpenMP.c //** Metodo Iterativo de Gauss-Seidel com paralelismo OpenMP #include <omp.h> #include <stdio.h> #include <stdlib.h> #include <math.h> # define nEquacao 200 int main() { int niTeracao=0,nExecucao=0; int i, j, k,z; int nTotalExecucao=5; double inicioRegiaoParalela,fimRegiaoParalela; double soma = 0; double tparalelo[5]; double tolerancia =0; double tmediaparalelo=0; double tdesviopadraoparalelo=0; // Desempenho do Metodo Iterativo de Gauss-Seidel printf("\nDigite a Primeira Interacao: "); scanf(" %i", &niTeracao); printf("\nDigite o Valor da Tolerancia: "); scanf(" %d", &tolerancia); double matriz[nEquacao][nEquacao]; double xv[nEquacao],x[nEquacao], xb[nEquacao]; printf("\nIniciando o metodo iterativo de GAUSS-SEIDEL\n"); printf("\nNo de Iteracoes %d \n",niTeracao); printf("\nNo de Equacoes %d \n",nEquacao); printf("\nNo de Execucoes %d \n",nTotalExecucao); printf("\n--------------------------------------------------------"); printf("\n\nAguarde! Gerando as Equacoes...\n\n"); // preenche a matriz for (i=0; i<nEquacao; i++) { xb[i]= (double) ((nEquacao+1)*i); x[i]=(double) 0.0; for (j=0; j<nEquacao; j++) matriz[i][j] = (double) ((i+j)*i); } for (z=1; z<=10; z++) { // Laço que define a qtde de execução for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { // ****** Inicio do Processo paralelo *************** // capta o tempo inicial inicioRegiaoParalela = omp_get_wtime(); #pragma omp parallel // Cria a região paralela { #pragma omp for nowait private(j,i) reduction (+:soma) for (k=1; k<=niTeracao; k++) {


82

for (i=0; i<nEquacao; i++) xv[i] =(double) x[i]; for (i=0; i<nEquacao; i++) { soma=0; for (j=0; j<nEquacao; j++) // somatario if (j != i) soma=soma+(matriz[i][j]*xv[j]); x[i] = (xb[i] - soma) / matriz[i][i]; } for (i=0; i<nEquacao; i++) { if (abs(xv[i] - x[i]) > tolerancia) { break; } } } } // Fim da Regiao Paralela // capta o tempo final da região paralela fimRegiaoParalela = omp_get_wtime(); tparalelo[nExecucao] = fimRegiaoParalela inicioRegiaoParalela; } // fim nº Total de Execução //******** Calcula Media Aritmetica e Desvio Padrao tmediaparalelo =0; // Calcula a media artimetica for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tmediaparalelo =tmediaparalelo+ tparalelo[nExecucao]; } tmediaparalelo = tmediaparalelo/ nTotalExecucao; tdesviopadraoparalelo =0; // Calcula o desvio padrão for (nExecucao=0; nExecucao<nTotalExecucao; nExecucao++) { tdesviopadraoparalelo =tdesviopadraoparalelo+ pow( (tparalelo[nExecucao] - tmediaparalelo),2); } tdesviopadraoparalelo =sqrt(tdesviopadraoparalelo /(nTotalExecucao1)); // impressão dos tempos seriais e parelos if (z==1) { printf("\n+--------------------+------------+------------+"); printf("\n| No de Iteracao |Paralela(s) | D Padrao(s)|"); printf("\n+--------------------+------------+------------+"); } printf("\n| %d | % 3.2f s | % 3.2f s |", niTeracao, tmediaparalelo,tdesvipadraoparalelo); printf("\n+--------------------+------------+------------+"); niTeracao = niTeracao + 10; } // Fim da Qtde Iteracao return 0; }


M01405