0

UNIJUI - UNIVERSIDADE REGIONAL DO NOROESTE DO ESTADO DO RIO GRANDE DO SUL

DCEEng – DEPARTAMENTO DE CIÊNCIAS EXATAS E ENGENHARIAS

PROCESSAMENTO PARALELO COM ACELERADORES GRÁFICOS

RODRIGO SCHIECK

Santa Rosa, RS - Brasil 2012

1

RODRIGO SCHIECK

PROCESSAMENTO PARALELO COM ACELERADORES GRÁFICOS

Projeto apresentado na disciplina de Trabalho de Conclusão de Curso do curso de Ciência da Computação da Universidade do Noroeste do Estado do RS como requisito básico para apresentação do Trabalho de Conclusão de Curso.

Orientador: Edson Luiz Padoin

Santa Rosa – RS 2012

2

PROCESSAMENTO PARALELO COM ACELERADORES GRÁFICOS

RODRIGO SCHIECK

Projeto apresentado na disciplina de Trabalho de Conclusão de Curso do curso de Ciência da Computação da Universidade do Noroeste do Estado do RS como requisito básico para apresentação do Trabalho de Conclusão de Curso.

______Orientador: Prof. Me. Edson Luiz Padoin

BANCA EXAMINADORA

______Prof. Me. Rogério Samuel de Moura Martins

Santa Rosa – RS 2012

3

“A mente que se abre a uma nova ideia jamais voltará ao seu tamanho original.”

Albert Einstein

4

Dedicatória

Aos meus pais Armindo e Alda, e a minha esposa Elenice, pessoas que amo muito e que me apoiaram e incentivaram em toda esta trajetória, tornando mais este sonho uma realidade, mas que não seja o último, e sim apenas mais um dos muitos outros que virão. Na indisponibilidade de tempo, o qual não pude estar com eles, pois tive que mediar entre o trabalho e o estudo, mas que daqui pra frente pretendo compensar.

5

AGRADECIMENTOS

Agradeço à Unijuí como um todo, pelo ótimo ambiente de ensino e corpo docente disponibilizado. Agradeço em especial aos meus pais, que além do auxílio financeiro me deram todo apoio e compreensão, sem o qual teria sido muito difícil superar algumas etapas desta longa trajetória. Agradeço à minha esposa, companheira compreensiva, que sempre esteve ao meu lado me dando apoio e incentivo amenizando o desgaste físico e mental decorrente das diversas horas consecutivas de estudo. Agradeço ao meu orientador, professor Edson Luiz Padoin que levo comigo não apenas como professor ou orientador, mas como amigo, que mesmo com muito trabalho pode me dar muita atenção, sem contar seus conselhos técnicos e científicos, sempre muito bem elaborados com uma ótima fundamentação. Soube ser muito compreensivo, mas também cobrou quando necessário. Mesmo não estando sempre disponível fisicamente, respondeu sempre quase que instantaneamente meus e-mails. Sua motivação e seu otimismo foram de grande apoio para que eu não abaixasse a cabeça perante as dificuldades. Muito obrigado pela parceria no desenvolvimento deste trabalho. Aos professores Gerson Battisti, Rogério Martins e Marcos Cavalheiro que serviram de base na construção do meu conhecimento, o qual foi de suma importância, pois foi empregado no desenvolvimento deste trabalho.

Muito Obrigado a Todos!

6

RESUMO

As GPUs estão se tornando cada vez mais presentes no cenário da computação de alto desempenho. Elas são processadores massivamente paralelos, inicialmente usados para processamento gráfico e jogos. Desde o surgimento da NVIDIA GeForce série 8, e a introdução do CUDA e outras ferramentas, as GPUs se tornaram programáveis, sendo capazes de executar aplicativos comuns e, com isso, através da modificação de algumas aplicações para algoritmos paralelos, conseguiram uma maior performance e escalabilidade dessas aplicações. As GPUs programáveis com suas arquiteturas massivamente paralelas expandiram o horizonte da computação de alto desempenho, tornando possível executar mais rapidamente algoritmos paralelos e com menor consumo energético. O objetivo deste trabalho é comprovar e demonstrar a melhor eficiência das GPUs quanto as CPUs em aplicações paralelas. Para isto, foram desenvolvidas aplicações utilizando CUDA e APARAPI para mostrar o desempenho da GPU assim como também aplicações que exigissem desempenho da CPU para fazer a comparação. Foi implementado um conjunto de oito algoritmos que utilizam técnicas diferentes de “stress” de ambas as arquiteturas. Os resultados dos testes foram submetidos a um processo de avaliação quanto à corretude e ao tempo de execução. Gráficos foram elaborados no intuito de analisar melhor e descrever o comportamento do sistema diante de diferentes recursos, como número de threads, números de processos, dimensões das matrizes, etc. A principal conclusão deste projeto foi que a definição da estratégia é decisiva para obtenção do menor custo de tempo, onde aplicações altamente paralelizáveis que executam uma única instrução sobre múltiplos dados, podem obter um ganho exponencial de desempenho utilizando-se de GPUs.

Palavras-chave: Processamento Paralelo, Arquitetura Heterogênea, Acelerados Gráficas, Alto Desempenho, CUDA, APARAPI, GPU, GPGPU.

7

ABSTRACT

GPUs are becoming increasingly present in the scenario of high performance computing. They are massively parallel processors, initially used for graphics processing and games. Since the emergence of the NVIDIA GeForce 8 series and the introduction of CUDA and other tools, GPUs became programmable, being able to run common applications and, thus, by modifying some applications for parallel algorithms, achieved a higher performance and scalability these applications. The programmable GPUs with their massively parallel architectures expanded the horizon of high performance computing by making it possible parallel algorithms run faster and with less energy consumption. The objective of this work is to prove and demonstrate the improved efficiency of GPUs as CPUs in parallel applications. For this, applications have been developed using CUDA and APARAPI to show the performance of the GPU as well as applications that require CPU performance to make the comparison. We have implemented a set of eight algorithms that use different techniques “stress” of both architectures. The test results were submitted to a review process regarding the correctness and runtime. Charts were developed in order to better analyze and describe the system behavior before different features, such as number of threads, number of processes, dimensions of arrays, etc. The main conclusion of this project was that the definition of the strategy is crucial to obtaining the lowest cost of time, where highly parallelizable applications running on a single instruction multiple data, may obtain a gain exponential performance using GPUs.

Keywords: Parallel Processing, Heterogeneous Architecture, Accelerated Graphics, High Performance, CUDA, APARAPI, GPU, GPGPU.

8

LISTA DE SIGLAS

API Application Programming Interface APARAPI A PARallel API APU Accelerated Processing Unit ARM Advanced RISC Machine CMP Chip Level Multithreading CPU CPD Centro de Processamento de Dados CUDA Compute Unified Device Architecture FLOPS Floating point Operations Per Second FPS Frames Por Segundo GPU GPGPU General Purpose on Graphics Processing Units ILP Instruction Level Parallelism IT Information Technology JVM Java Virtual Machine PC Personal Computer PCI Peripheral Component Interconnect SDK Software Development Kit SIMD Single Instruction Multiple Data SM Streaming Multiprocessor SP Streaming Processors TI Tecnologia da Informação TLP Thread Level Parallelism

9

LISTA DE IMAGENS

Figura 1: Supercomputador TITAN ...... 21 Figura 2: Novo design SM...... 30 Figura 3: Diferença de cores entre uma CPU e uma GPU...... 33 Figura 4: Comparação entre chips CPU e GPU...... 35 Figura 5: M2090...... 37 Figura 6: Desempenho NVIDIA Tesla...... 38 Figura 7: Evolução Das GPUs...... 39 Figura 8: NVIDIA gpu Roadmap...... 40 Figura 9: Tabela De Especificações...... 41 Figura 10: Arquitetura de uma APU...... 43 Figura 11: Plataforma Java...... 46 Figura 12: HelloWord Java...... 47 Figura 13: Comparação de desempenho...... 49 Figura 14: Modelo de Navier-Stokes...... 50 Figura 15: Método de Lattice Boltzman...... 50 Figura 16: Modelo de Programação CUDA...... 51 Figura 17: Modelo de Memória CUDA...... 52 Figura 18: Memórias CUDA...... 53 Figura 19: Paralelismo Dinamico...... 55 Figura 20: Mandelbrot...... 55 Figura 21: OpenCL...... 58 Figura 22: GTX 465 ...... 61 Figura 23: Conjunto de Mandelbrot ...... 62 Figura 24: Simulação de Corpos...... 63

10

LISTA DE TABELAS

Tabela 1: Unidades de Medida de Desempenho ...... 27 Tabela 2: Hardware ...... 60 Tabela 3: Detalhes da GPU ...... 60 Tabela 4: Software ...... 61

11

SUMÁRIO

INTRODUÇÃO ...... 13

1.1 JUSTIFICATIVA ...... 16 1.2 OBJETIVO GERAL ...... 17 1.3 OBJETIVOS ESPECÍFICOS ...... 18 1.4 METODOLOGIA E PROCEDIMENTOS ...... 18 1.5 ORGANIZAÇÃO DO TRABALHO ...... 19 1.6 TRABALHOS RELACIONADOS ...... 20

2 COMPUTAÇÃO DE ALTO DESEMPENHO ...... 21 2.1 PROCESSAMENTO PARALELO ...... 23 2.2 ARQUITETURAS DE FLYNN ...... 24 2.2.1 SIMD ...... 24 2.3 MEDIDAS DE DESEMPENHO ...... 25 2.3.1 FLOPS ...... 27 2.4 STREAM PROCESSORS ...... 28 2.5 GREEN IT ...... 28

3 HARDWARE ...... 32 3.1 CPU ...... 32 3.2 GPU ...... 33 3.2.1 GPGPU ...... 35 3.2.2 Projeto Denver ...... 38 3.2.3 Evolução das Placas Gráficas NVIDIA ...... 39 3.4 APU ...... 42 3.5 COMPARAÇÃO ...... 43

4 SOFTWARE ...... 45 4.1 LINGUAGENS DE PROGRAMAÇÃO ...... 45

12

4.1.1 C/C++ ...... 45 4.1.2 Java ...... 46 4.2 DESENVOLVIMENTO PARA GPU ...... 47 4.2.1 CUDA ...... 47 4.2.2 CUDA 5 ...... 54 4.2.3 APARAPI ...... 56 4.2.4 OpenCL ...... 58

5 AMBIENTE DE TESTES ...... 60 5.1 HARDWARE ...... 60 5.2 SOFTWARE ...... 61 5.3 Benchmarks ...... 62 5.3.1 O Conjunto de Mandelbrot ...... 62 5.3.2 Simulação de Corpos ...... 63

6 RESULTADOS ...... 64 6.1 CUDA ...... 64 6.1.1 Cálculo de Números Perfeitos ...... 64 6.1.2 Cálculo de Números Primos ...... 65 6.1.3 Multiplicação de Matrizes ...... 65 6.1.4 Conjunto de Mandelbrot ...... 66 6.1.5 Simulação de Corpos ...... 67 6.2 APARAPI ...... 67 6.2.1 Cálculo de Números Perfeitos ...... 67 6.2.2 Cálculo de Números Primos ...... 68 6.2.3 Multiplicação de Matrizes ...... 69 6.2.4 Conjunto de Mandelbrot ...... 69 6.3 Comparação entre as APIs ...... 70 6.3.1 Cálculo de Números Perfeitos ...... 70 6.3.2 Cálculo de Números Primos ...... 71 6.3.3 Multiplicação de Matrizes ...... 71

CONCLUSÃO ...... 72

REFERÊNCIAS BIBLIOGRAFICAS ...... 74

OBRAS CONSULTADAS ...... 80

ANEXOS ...... 81

13

INTRODUÇÃO

Maior velocidade e maior precisão são, e sempre serão proporcionados por cientistas e engenheiros da computação. Para alcançar essas metas, processadores single-core foram montados para chegar à "computação paralela". Este paralelismo recentemente se estendeu ao nível do chip, com o surgimento de arquiteturas multi- core, que é, a grosso modo, a adição de mais núcleos em um único chip em ambas as unidades centrais de processamento (CPUs) e unidades de processamento gráfico (GPUs). O último e mais recente é um candidato possivelmente mais forte do que o anterior para resolver os problemas computacionalmente exigentes. Isto é devido ao fato de a capacidade de ponto flutuante computacional das GPUs torna-se geralmente 10 vezes mais elevado do que CPUs. Como resultado, a computação científica está se movendo rapidamente em direção ao paralelismo multi-core. Modelagem e simulação em ciência e engenharia nucleares dependem muito de poder computacional. Engenheiros nucleares se aproveitaram da paralelização para avançar teste e simulações de neutrônica, hidráulica térmica, materiais, física de plasma, fluídos, química quântica, física espacial, etc. Todos os sistemas de computação, de móveis a supercomputadores, estão se tornando computadores paralelos heterogêneos utilizando ambos os CPUs multi- core e as GPUs multi-thread para maior eficiência de energia e taxa de computação. Enquanto a comunidade de computação está correndo para construir ferramentas e bibliotecas para facilitar a utilização destes sistemas heterogêneos de computação paralela, eficaz e utilização segura desses sistemas vai sempre exigir conhecimentos sobre as interfaces de programação de baixo nível nestes sistemas. A evolução do hardware tem sua teoria da evolução que é relativa às necessidades humanas e mais ainda as necessidades de softwares. Portanto partindo deste entendimento o hardware deve estar em uma constante transformação para que supra as necessidades dos softwares, onde o inverso também é valido, pois o software também deve atender as necessidades do hardware, sendo assim estas duas tecnologias devem andar paralelamente. Desse modo, para que tenhamos um hardware que atenda um software de alta complexidade computacional, se faz necessário que ele seja escalável e agregue um alto poder de processamento.

14

Está ficando cada vez mais difícil fazer com que os computadores funcionem com maior velocidade apenas aumentando a frequência do relógio(clock) por causa de problemas com a maior dissipação de calor e outros fatores, mas também principalmente respeitando a eficiência energética do equipamento, Em vez disso, projetistas estão buscando o paralelismo para conseguir grandes ganhos de velocidade aproveitando os milhares de núcleos de processamento disponíveis nos ambientes de supercomputação atuais. Primeiro foi o paralelismo em nível de instrução(ILP) e como exemplo temos o pipeline, depois o paralelismo em nível de threads(TLP), capaz de executar mais de uma thread (processo) simultaneamente e por fim surge o paralelismo em nível de chip que possibilita a execução simultânea de mais de um fluxo de instruções.

O paralelismo é um paradigma que visa extrair o máximo desempenho possível durante a realização de uma tarefa, através da exploração das características de simultaneidade presentes na carga de trabalho e no ambiente em que se está trabalhando (EL-REWINI, 2005; ABD-EL-BARR, 2005).

O paralelismo pode ser introduzido em muitos níveis diferentes, desde o muito baixo, onde os elementos de processamento são muito fortemente acoplados, até o muito alto, onde eles são fracamente acoplados, a seguir será abordado os 3 principais níveis de paralelismo conforme define Volpato (2011) em seu projeto de graduação. No nível baixo está o paralelismo no chip, no qual as atividades paralelas ocorrem em um único chip. Uma forma de paralelismo no chip é o paralelismo no nível de instrução, no qual uma instrução, ou uma sequência de instruções, emite múltiplas operações que podem ser executadas em paralelo por diferentes unidades funcionais. Uma segunda forma de paralelismo no chip é o multithreading, no qual uma CPU pode comutar como quiser entre múltiplos threads, instrução por instrução, criando um multiprocessador virtual. Uma terceira forma de paralelismo no chip é o multiprocessador de chip único(CMT), ou seja, dois ou mais núcleos são colocados no mesmo chip o que permite que eles executem operações simultaneamente. Em um nível acima encontramos os co-processadores, ou aceleradores, que normalmente são placas de expansão que agregam capacidade de processamento extra em alguma área especializada tal como processamento gráfico. Este será o

15 foco desta pesquisa, avaliando o quão benéfico se torna a utilização deste tipo de co-processamento. Em um próximo nível encontramos os multiprocessadores de memória compartilhada. Estes tipos de sistemas fazem uso de duas ou mais CPUs totalmente desenvolvidas que compartilham uma memória em comum. Nesta breve explanação sobre os tipos de paralelismo, é possível observar a flexibilidade da computação e o quão mutável ela é, onde quem quiser acompanhá- la não poderá prender-se a nada específico. Pensando nas arquiteturas para computação de alto desempenho, como o próprio nome sugere se entende que o foco é alto desempenho. Para isto, há muito tempo vem-se usando arquiteturas homogêneas, onde a unidade de processamento era exclusivamente o processador(CPU), mas com os avanços da tecnologia e a evolução das aceleradores gráficos, foi se pensando nelas como uma grande auxiliar ao processamento paralelo, o co-processamento, devido a sua escalabilidade e em muitos casos com maior poder de processamento. Assim surgiram as arquiteturas heterogêneas que fazem uso de CPUs, GPUs e as APUs, para realizarem o processamento das tarefas. A GPU (Graphics Processing Unit) é uma placa aceleradora de imagem otimizada para paralelismo e renderização de imagens. Devido a essa otimização, a GPU atinge picos de velocidade que supera qualquer CPU existente. Por exemplo, uma GPU NVIDIA Fermi pode ser cerca 250 vezes mais veloz que um Intel Core i7 utilizada de forma sequencial. Como exemplo, no TOP500, a classificação dos 500 computadores mais rápidos do mundo, onde vários dos computadores mais bem colocados já fazem uso desta arquitetura revolucionária, são 62 na 40ª lista. Em sua classificação, Michael J. Flynn(1966-1972), definiu que tais arquiteturas paralelas fazem uso da classe SIMD, onde se trabalha com uma instrução sobre múltiplos conjuntos de dados. Para que possamos usufruir de todo o poder do hardware que temos em mãos precisamos de algoritmos específicos. Estes algoritmos farão a divisão entre as tarefas da CPU e GPU ou da APU conforme a arquitetura utilizada. Utilizando uma GPU NVIDIA dispomos da API CUDA que requer programação em C. Recentemente chegou as mãos do desenvolvedores uma nova API de código aberto

16 disponibilizada pela AMD, a APARAPI, ele nada mais é do que uma biblioteca Java para paralelismo com GPUs e APUs. É nesta base que se dará esta pesquisa focando exclusivamente a esta arquitetura heterogênea, mais especificamente de como fazer total uso das aceleradores gráficos.

1.1 JUSTIFICATIVA

O desempenho de processamento paralelo de uma GPU é relativamente maior comparado a CPU devido à sua arquitetura massivamente paralela, logo, já está otimizada para atividades que demandem de processamento paralelo. Holz (2010), em seu trabalho de conclusão de curso, realizou testes sobre uma plataforma com uma GPU e mesmo com uma arquitetura relativamente inferior, conseguiu um ganho de performance de 78% nos algoritmos processados pela GPU. Há relatos de testes onde se obteve ganhos muito maiores, claro que não na mesma arquitetura. Jack Dongarra, especialista em supercomputadores e professor da Universidade do Tennessee, em seu relato sobre estas arquiteturas heterogêneas, disse:

As GPUs evoluíram ao ponto de permitirem que muitos aplicativos do mundo real sejam facilmente implementados e executados de forma significativamente mais rápida do que em sistemas com vários núcleos. As futuras arquiteturas de computação serão sistemas híbridos, com GPUs de núcleo paralelo operando em conjunto com CPUs de vários núcleos (DONGARRA).

Por se tratar de um processador vetorial, onde geralmente existe um número expressivo de núcleos se comparando com as CPUs convencionais, claro que são processadores de arquiteturas diferentes, mas ela pode facilmente substituir uma quantidade significativa de CPUs. Uma arquitetura paralela usando GPU tem um melhor aproveitamento de memória pelo fato de cada GPU possuir sua própria memória, mas o que não a impede de usar a memória principal do sistema. GPUs possuem maior desempenho em operações com ponto flutuante, não apenas com ponto flutuante e sim também nas operações gerais que envolvem

17 cálculos numéricos(vetoriais), assim, possibilitando execução em tempo polinomial de algoritmos relativamente custosos desde que tenham pouca dependência de dados. Em 2010 subiu ao primeiro lugar do TOP500 um novo supercomputador, o Tianhe-1A produzido pela empresa NUDT, ele é composto por GPUs NVIDIA e CPUs Xeon, totalizava 186368 cores oque gerava um consumo de 4 megawatts. A NVIDIA afirma que se o mesmo número de cores fosse atingido utilizando apenas CPUs, o consumo dele ultrapassaria os 12 megawatts e que com o processamento paralelo fornecido pelas GPUs ele consegue ser três vezes mais eficiente e com um consumo três vezes menor. Segundo a NVIDIA a versão mais recente da CUDA Toolkit (4.1) oferece diversos novos recursos e melhorias que visam ser explorados. A AMD, não querendo ficar atrás, também lançou a APARAPI para o uso de paralelismo em aplicações Java e promete aos desenvolvedores um código mais simplista e fácil de usar. Somente os testes práticos dirão se a mais nova concorrente da CUDA tem capacidade o suficiente para atuar entre as grandes nesta área. Arquiteturas heterogêneas têm maior custo benefício e melhor aproveitamento de espaço interno físico porque podem ser usadas diversas GPUs em uma mesma placa-mãe. Levando em conta o fato de que é possível usar diversas GPUs sobre uma mesma placa-mãe, somamos o número de núcleos de todas elas mais os núcleos da(s) CPU(s) locais, logo, teremos um número 'x' de núcleos locais para os quais são possíveis atribuir tarefas, onde estes estão interligados por barramentos de alta velocidade. Porém não seria o mesmo se usássemos apenas CPUs, já que para atingir este mesmo número de núcleos teríamos que interliga-las através de um barramento de rede o qual não atingiria a mesma velocidade. Além de tudo isso ainda possuímos o fato de que o mercado atual, principalmente o de computadores portáteis, está apostando nas APUs, elas são processadores híbridos que contém em si uma CPU e uma GPU.

1.2 OBJETIVO GERAL

Mostrar que arquiteturas heterogêneas podem atingir maior performance comparadas a arquiteturas convencionais ou tradicionais.

18

1.3 OBJETIVOS ESPECÍFICOS

 Usar a arquitetura heterogênea de co-processamento com uma GPU como co-processador. Esta prática deverá reduzir a carga da CPU e transferi-la para a GPU na execução dos algoritmos de testes.  Executar algoritmos e benchmarks, que demandam de um alto poder computacional, paralelamente processados, exibindo, através dos resultados, que o co-processamento com GPU é uma ótima alternativa ao processamento paralelo, pois espera-se reduzir o tempo de execução dos mesmos.  Realizar os testes sobre a última versão da API de desenvolvimento CUDA da NVIDIA, que apresenta diversas melhorias e novos recursos.  Comparar a performance entre a última versão da CUDA e da APARAPI.  Mostrar que a arquitetura heterogênea juntamente com o ganho de desempenho também proporciona um melhor aproveitamento de espaço físico estrutural, ou seja, mais performance em um menor espaço. Em segundo plano existem alguns objetivos indiretos que podem ser alcançados com o uso de uma arquitetura heterogênea, como por exemplo algoritmos hoje ainda não solucionáveis em um espaço de tempo relativamente baixo.

1.4 METODOLOGIA E PROCEDIMENTOS

Através de pesquisas bibliográficas, foram coletar dados sobre as arquiteturas heterogêneas e homogêneas que serviram de base para a realização do projeto. De base desta pesquisa, o próximo passo foi montar o hardware necessário, sobre o qual foram realizados os testes de performance para a coleta de dados. Com o hardware funcionando devidamente, foi instalado o ambiente de desenvolvimento C, JAVA e CUDA. Implantada a arquitetura, está tudo pronto para começar os testes práticos de desempenho da arquitetura com algoritmos relativamente custosos que

19 causaram um “stress” na CPU e na GPU elevando ao máximo o processamento de ambas. Os dados resultantes dos testes efetuados foram registrados e analisados minuciosamente, pois serviram de base para comparar as duas arquiteturas. Os mesmos testes foram utilizados com a mais nova alternativa ao processamento em GPU, a APARAPI. Os dados dos testes foram coletados de ambas APIs para fazer uma análise comparativa. Por fim, a proposta é apresentar os resultados obtidos nesta pesquisa em eventos específicos da área.

1.5 ORGANIZAÇÃO DO TRABALHO

Esta pesquisa está subdividida em 5 capítulos, além do capítulo introdutório, que melhor descrevem a tecnologia e os recursos utilizados. O capítulo 2, Computação de Alto Desempenho, descreve o mundo da supercomputação, as supermáquinas que são utilizadas nos grandes CPDs para pesquisas e simulações, suas demandas e suas limitações. Seguindo para o capítulo 3, o tema tratado passa a ser o HARDWARE, o equipamento físico aqui é abordado como um fator decisivo para se atingir os resultados esperados. São detalhadas as caraterísticas das CPUs e das GPUs, com o intuito de demonstrar o que a GPU oferece de melhor em termos de computação paralela. Em contra partida temos o SOFTWARE, abordado no capítulo 4. Esta seção trata do software como uma ferramenta lógica, pois na verdade ele é uma API em si que provê meios de se programar o hardware. Não ficando apenas na teoria, a pesquisa põe em prática a teoria estudada, almejando comprovar a eficiência da estrutura. Para que isto seja possível, foram implantados os ambientes de hardware e software descritos no capítulo 5, Ambiente de Testes. Por fim, sobre o ambiente implantado realizou-se diversos testes com algoritmos de benchmark, onde os resultados coletados encontram-se reunidos no capítulo 6, Resultados.

20

1.6 TRABALHOS RELACIONADOS

Diversos trabalhos e pesquisas já foram feitas nesta área, alguns deles mais especializados com maiores resultados e outros mais abrangentes, mas todos com o mesmo intuito de mostrar os benefícios das GPUs. Vale destacar alguns destes trabalhos, como o do Lucas Holz de 2010, aplicado no contexto da irrigação e de absorção da água pelo solo, onde ele conseguiu uma performance 5 vezes maior apesar de ter utilizado uma GPU de notebook.

Com a utilização desta tecnologia e do grande poder de processamento paralelo das GPUs conseguiu-se resolver o algoritmo em menores tempos de execução, aumentando assim a precisão dos resultados, utilizando malhas compostas de um maior número de pontos(HOLZ, 2005).

Outro trabalho como o de Sérgio Ricardo Dos Santos Moraes também merece uma atenção especial, devido aos ótimos resultados obtidos e ao uso de múltiplas GPUs.

Reescrever um código sequencial em paralelo nem sempre é possível e fácil; o problema tem de ser paralelizável. O objeto do presente trabalho – problema do transporte de nêutron resolvido por simulação através do método de Monte Carlo – teve uma excelente performance em relação à solução sequencial. Tomando-se a água como exemplo, a solução paralela para um número de histórias de nêutrons igual a 1010 foi mais de 300 vezes mais rápida para uma GPU e mais de 2.000 vezes para oito GPUs, em relação à solução sequencial. As diferenças encontradas nas soluções de uma para duas GPUs se mantiveram também para múltiplas GPUs (MORAES, 2012).

Como é possível notar, ambos trabalhos focam em uso de algoritmos de paralelismo, mas também reforçam que o ganho de performance só é possível quando o contexto do problema possibilita a paralelização da tarefa.

21

2 COMPUTAÇÃO DE ALTO DESEMPENHO

Supercomputação é um serviço de combinação dos recursos computacionais disponíveis para atingir o mais alto nível de desempenho em computação. Os primeiros supercomputadores foram criados na década de 1960 pelo engenheiro Seymour Cray para a extinta Control Data Corporation. Seymour Cray posteriormente fundou uma empresa que leva o seu nome, a Cray Research, e dominou o mercado da supercomputação durante cerca de 25 anos, até o início dos anos de 1990. Os Cray ainda são estrelas no mundo da supercomputação, mas hoje a concorrência é grande, com empresas como HP, Oracle, IBM, entre outras. (GEEK.COM.BR, 2011).

Figura 1: Supercomputador TITAN Fonte: http://creep.ru/1161053125-cray-titan-moschneyshiy-superkompyuter-mira.html

Um supercomputador, como na figura 1, é um computador cujo desempenho é extremamente alto. A velocidade de processamento destas máquinas supera em milhões de vezes a de um computador doméstico. Ele também precisa ter uma capacidade de memória absurdamente grande para dar conta da enorme quantidade de dados apresentados na entrada e depois produzidos na saída. A maioria dos supercomputadores usa um tipo de processamento de informação chamado de

22 processamento paralelo, o que quer dizer que pode calcular várias coisas ao mesmo tempo. Embora essa capacidade de processamento possa parecer fútil, os supercomputadores são bastante úteis. Cálculos e simulações científicas que poderiam levar anos podem ser feitas em questão de dias ou mesmo horas. Por conta disso, os supercomputadores têm lugar cativo em centros de pesquisa, sejam elas aeroespaciais, militares, de física, química e medicina. Titan, o supercomputador que está ilustrado na figura 1, deverá se tornar o mais rápido do mundo, construído pelo Laboratório Nacional de Oak Ridge, localizado nos Estados Unidos, seu desempenho de 20 petaflops sendo aproximadamente 90% oriundos dos 18.688 aceleradores da Tesla K20. O TITAN é a evolução do antigo que possui 2,3 petaflops, ele é 10 vezes mais rápido e cinco vezes mais eficiente do ponto de vista energético do que seu predecessor. Caso o Oak Ridge tivesse atualizado o JAGUAR através da simples expansão de sua arquitetura baseada em CPU, o sistema teria mais de quatro vezes seu tamanho atual e consumiria mais de 30 megawatts de energia (SUPERCOMPUTADOR Titan...,2012). As previsões de tempo hoje são muito mais precisas do que há trinta ou mesmo vinte anos atrás. Isso ocorre porque os institutos de meteorologia e clima como o INPE do Brasil, não mais tentam “adivinhar” o que vai acontecer pela configuração prévia da atmosfera. Em vez disso, um supercomputador simula, baseado nas condições atuais e nas tendências anotadas, como o clima realmente estará. A quantidade de cálculos por segundo para essas simulações é muito alta, e deve acontecer em poucas horas, caso contrário, não seria possível anunciar a tempo um alerta de furação ou de maremoto, por exemplo. Da mesma forma, apenas o imenso poder dos supercomputadores tornam práticas e possíveis as pesquisas científicas e simulações de armas nucleares, modelagem química e molecular e projeto de aeronaves e carros de corrida. Com os supercomputadores cada vez mais poderosos, no caso da previsão do tempo, é possível fazê-la com uma maior precisão.

23

2.1 PROCESSAMENTO PARALELO

O grande segredo dos supercomputadores é o seu alto poder de processamento. Além de muita memória, eles possuem inúmeros processadores que operam juntos. O processamento paralelo possibilita ao computador fazer várias tarefas simultaneamente, mas isto só é possível se a arquitetura possuir mais de um processador que realize as tarefas em paralelo. Para que todos esses processadores trabalhem em cooperação, é necessário que, em primeiro lugar, tenham algum tipo de comunicação entre si. Essa comunicação pode ser direta como se fosse um computador só contendo todos os processadores ou através de uma conexão de rede entre vários computadores. Mas não basta se interligar computadores para ter um supercomputador. É preciso que o sistema operacional em cada um deles seja preparado para isso, e ainda que haja um software central para distribuir as tarefas entre todos eles. Ou seja, é preciso ter um software preparado para operar sobre uma arquitetura de multiprocessadores. (GEEK, 2011). Um processo é definido como um programa em execução. Todo processo possui um fluxo de execução. Por sua vez, uma thread nada mais é do que um fluxo de execução. Na maior parte das vezes, cada processo é formado por um conjunto de recursos mais uma única thread e é justamente este paradigma que deve ser quebrado para que o software usufrua de todo poder computacional da arquitetura, acrescentando mais threads para dividir o processamento em pequenas partes, as quais serão processadas pelos diversos processadores. O paralelismo pode ser implementado em diversos níveis, são eles: Paralelismo em nível de instrução(ILP) - é um conjunto de técnicas de desenvolvimento de processadores e compiladores que aumentam o desempenho das aplicações, fazendo com que operações sejam executadas em paralelo. No nível de instruções o paralelismo é explorado por arquiteturas de processadores capazes de executar instruções ou operar dados em paralelo, como o pipeline de instruções. (YAMASHIRO E CORREA). Paralelismo em nível de thread(TLP) – é a utilização de várias linhas de execução simultâneas (multithreading). Com ele é possível ter várias threads compartilhando, de forma intercalada, as mesmas unidades funcionais de um processador, onde o processador duplica o estado independente de cada thread. (MOREIRA et al).

24

Paralelismo em nível de chip(CMP) - o processador possibilita a execução simultânea de mais de um fluxo de instruções, que é o caso dos processadores multicore que possuem 2 ou mais núcleos de processamento, lhes atribuindo a capacidade de executarem blocos de código simultaneamente. Logo, em teoria, quanto mais núcleos um processador tiver, mais tarefas em paralelo ele será capaz de executar. Por outro lado, este nível não acelera a execução quando possuímos apenas 1 tarefa a processar.

A utilização do paralelismo nos projetos de arquitetura de computadores tem possibilitado um aumento significativo na velocidade de processamento devido à execução simultânea de diversas tarefas. Contudo, os aspectos relacionados ao software paralelo e à paralelização dos programas são essenciais para o desempenho do sistema paralelo. (FERLIN, 2011).

2.2 ARQUITETURAS DE FLYNN

Segundo a taxonomia de Michael J. Flynn(Taxonomia de Flynn), introduzida entre 1966 a 197, onde foi definido um dos primeiros sistemas de classificação para computadores e programas paralelos e sequenciais. Em sua classificação ele separou as arquiteturas de computadores em 4 classes, que são elas:  SISD (Single Instruction Single Data): Fluxo único de instruções sobre um único conjunto de dados.  SIMD (Single Instruction Multiple Data): Fluxo único de instruções em múltiplos conjuntos de dados.  MISD (Multiple Instruction Single Data): Fluxo múltiplo de instruções em um único conjunto de dados.  MIMD (Multiple Instruction Multiple Data): Fluxo múltiplo de instruções sobre múltiplos conjuntos de dados. (TANENBAUM, 2001).

2.2.1 SIMD

A sigla SIMD (Single Instruction, Multiple Data), que, em português, quer dizer Uma Instrução, Múltiplos Dados, descreve um método de operação de computadores com várias unidades de processamento em paralelo. Neste método, a

25 mesma instrução é aplicada simultaneamente a diversos dados para produzir mais resultados. Computadores SIMD são utilizados para a resolução de problemas computacionalmente intensivos da área científica e de engenharia, em que existem estruturas de dados regulares como vetores e matrizes. Os mesmos também estão em uma categoria importante, devido a fatores como: simplicidade de conceitos e programação, regularidade da estrutura, facilidade de escalabilidade em tamanho e desempenho, aplicação direta em uma série de aplicações que requerem paralelismo para obter o desempenho necessário.

2.3 MEDIDAS DE DESEMPENHO

O ponto principal da construção de um computador paralelo é fazer com que ele execute mais rapidamente do que uma máquina com um único processador. Se ele não cumprir esse objetivo, não vale a pena tê-lo. Além disso, ele deve cumprir o objetivo de maneira efetiva em relação ao custo. Uma máquina que é duas vezes mais rápida do que um uniprocessador a 50 vezes o custo muito provavelmente não será um sucesso de vendas. O modo mais direto de adicionar desempenho é adicionar Unidades de Processamento ao sistema. Contudo, essa adição deve ser feita de um modo tal que evite a criação de gargalos. Um sistema no qual se pode adicionar mais Unidades de Processamento e obter mais capacidade de computação correspondente é denominado escalável. Porém o mais conhecido é o aumento da frequência do relógio(clock), esta prática produz resultados consideráveis no ganho de desempenho, porém tem algumas implicações onde se destaca principalmente a grande dissipação de calor gerado pela unidade de processamento, calor o qual pode danificar o hardware se não controlado. Sendo este difícil de controlar, logo, parte-se para outras alternativas.

Vale mencionar que, na teoria, quanto mais núcleos tem uma CPU, maior o número de transístores e, por consequência, melhor sua performance. Mas, na prática, isto não acontece pelo principal motivo: o software está anos atrás do hardware. Uma CPU com 4 núcleos pode perder em performance nos games pelo fato do software ser otimizados para 2 núcleos. A programação em paralelo para 4 núcleos significa aumentar o problema, sem contar na otimização dos compiladores para fazer uso do paralelismo (CUDA PROGRAME..., 2009).

26

A definição de alto desempenho em computação é característica fundamental de um Supercomputador. Em 2004 foi dada pela IBM a seguinte definição a respeito dos supercomputadores: "Supercomputadores são máquinas construídas sob encomenda, com capacidade de processamento grande o suficiente para resolver complexos problemas referentes às aplicações para as quais foram desenvolvidos". Os supercomputadores são construídos para obter alto desempenho, são máquinas que possuem alto poder de processamento e sua arquitetura totalmente voltada para a obtenção de resultados específicos ao tipo de tarefa a ser realizada. Nesta categoria de computadores encontramos:  Processadores vetoriais paralelos (PVP);  Multiprocessadores simétricos (SMP);  Máquinas massivamente paralelas (MPP);  Máquinas com memória compartilhada distribuída (DSM);  Redes de estações de trabalho (NOW);  E as máquinas agregadas (COW/Cluster). Estas super máquinas, por assim dizer, se fazem cada vez mais necessárias, principalmente pelos constantes avanços tecnológicos e do mundo da pesquisa, onde é indispensável um cenário computacional para que realize as tarefas que demandem de um maior poder computacional. Mas não basta apenas ter um supercomputador, pois o que se espera destas “máquinas” é um altíssimo poder de processamento, onde são capazes de serem realizados milhares de cálculos de ponto flutuante por segundo, desempenho o qual não costumamos ver em qualquer computador, principalmente naqueles que usamos em casa, os conhecidos PCs. O poder de processamento dos supercomputadores sempre se deu ao grande número de processadores empregados na arquitetura. Tal metodologia já fez muitas pessoas pensarem, e movidos pelo desejo de um desempenho sempre maior, chegaram a uma simples palavra, GPU. Existem alguns supercomputadores com desempenho espantoso, mas costumam ocupar salas inteiras de grandes laboratórios, além de representarem investimentos milionários.

27

Em 2009, a equipe do grupo de pesquisa ASTRA, do Vision Lab, na Universidade da Antuérpia, na Bélgica, anunciou a criação do FASTRA II, um supercomputador de mesa, equipado com um processador Intel Core i7 e nada menos que sete placas de vídeo NVIDIA. Como resultado, o equipamento atinge um desempenho de 12 teraflops (UOL, 2009). O hardware foi montado dentro de um gabinete de PC comum e suas especificações incluem um processador Intel Core i7 920, 12 GB de RAM, 1 TB de espaço em disco, uma placa de vídeo GeForce GTX 275 e seis "dual GPU" GeForce GTX 295, totalizando 13 GPUs. Tudo isso ligado a uma placa-mãe ASUS P6T7 WS em um gabinete Lian-Li PC-P80. (UOL, 2009). Para aguentar todo este hardware, os cuidados com a energia não ficaram de fora. O supercomputador conta com quatro fontes de alimentação. Além disso, o sistema ainda precisou de uma BIOS especial, desenvolvida em colaboração com a ASUS. (UOL, 2009). Para quem pensa que o supercomputador custou um valor extraordinário, está enganado, o valor total dele não ultrapassou os R$18 mil e todo design da arquitetura do projeto é aberto, ou seja, quem dispor deste valor pode recriar o projeto em casa, sem contar que a equipe fornece todo o esquema do hardware e a cópia do software.

2.3.1 FLOPS

FLOPS é um acrônimo na computação que significa FLoating-point Operations Per Second, que, em português, quer dizer operações de ponto flutuante por segundo. Esta é uma medida de desempenho que indica o número de operações e cálculos de ponto flutuante que um computador é capaz de executar por segundo, mais especificamente no campo de cálculos científicos, que fazem grande uso de cálculos com ponto flutuante. Por exemplo, um teraflop corresponde a um trilhão de operações por segundo, hoje já estamos ultrapassando esta marca e chegando a escala de petaflops, 1015 instruções por segundo. Mais unidades de medidas de FLOPS estão listadas na tabela 1.

Tabela 1: Unidades de Medida de Desempenho Computador Desempenho

28

Nome FLOPS megaflop 105 gigaflop 109 teraflop 10¹² petaflop 1015 exaflop 1018 zettaflop 1021 yottaflop 1024

2.4 STREAM PROCESSORS

Stream Processors é o resultado da unificação de duas arquiteturas, o Pixel Shader e o Vertex Shader. Antigamente os processos de Pixel Shader e Vertex Shader eram feito de modo separado. O Pipeline ou processava somente Pixel Shader, ou somente Vertex Shader. As unidades de Vertex só processavam vértices, as de pixel apenas aplicavam shaders em pixels. O problema deste modelo é que as aplicações podem exigir mais operações de Pixel Shader do que de Vertex Shader e/ou vice-versa, logo, ocorre ociosidade do conjunto para o processo das instruções. (Tudo sobre Arquitetura..., 2009). Com o surgimento dos Stream Processors este conceito mudou, pois em vez de fazer o processamento separadamente, ele unificou em paralelo o tratamento dos vértices e pixels, ou seja, um stream processor pode então processar tanto Pixel Shader como também Vertex Shader. Basicamente, cada uma destas unidades age como um pequeno processador de cálculos de ponto flutuante independente, que pode ser programado para executar praticamente qualquer tipo de operação. Isso abriu as portas para o uso da GPU como processador auxiliar. (MORIMOTO, 2010).

2.5 GREEN IT

Cada vez mais, empresas de todo o mundo estão se preocupando com o desenvolvimento sustentável da sociedade e não a enxergam como uma boa ação, mas como obrigação que é cobrada pelos consumidores de seus produtos e, na

29 computação, isso não é diferente. A área da tecnologia da informação tem uma grande preocupação com a sustentabilidade, o reaproveitamento e economia energética e estas ações são conhecidas como Green Computing, Green IT (ou TI Verde no Brasil). O termo Green IT começou a ser usado em 1992, depois que a Agência de Proteção Ambiental dos Estados Unidos desenvolveu o projeto “Energy Star” com o objetivo de reconhecer a eficiência energética de diversas tecnologias. Green IT significa trabalhar o que produzimos na computação em questão de dispositivos, hardwares e até mesmo softwares de modo que eles consigam ser reaproveitados, remanejados e reciclados facilmente. Green IT abrange desde se produzir equipamentos com uma melhor eficiência energética ao desenvolvimento de softwares mais eficientes, mas que exijam menos de seu hardware. Se os níveis atuais de consumo forem mantidos, os gastos com eletricidade podem chegar a 50% dos orçamentos de tecnologia de uma grande empresa, segundo estima a consultoria Gartner Group. Não é por acaso que um dos principais motores da inovação no mundo da computação é a revolução verde. A Gartner calcula que o desperdício de energia nessas instalações possa chegar a 60%, e é justamente esse ponto que tem sido alvo da atenção da indústria tecnológica.

Desde o início desta década, com a expansão da internet e a crescente digitalização dos negócios, o número de servidores em uso no mundo passou de 6 milhões para 28 milhões. A maioria deles fica em grandes data centers, que podem ocupar áreas de até 5.000 metros quadrados e consomem até 50 vezes mais energia elétrica do que um escritório tradicional. (Teixeira, 2007).

O consumo energético é um dos principais desafios para a TI a nível empresarial. O consumo de energia dos datacenters representa 1,5% de todo o consumo de eletricidade nos EUA. Essas empresas procuram cada vez mais soluções que lhes permitam reduzir os custos energéticos e o consumo, ao mesmo tempo em que mantêm os níveis de serviço e aumentam a capacidade de resposta para que o negócio não pare. As CPUs são os componentes que mais consomem energia em servidores, portanto, os modelos de CPU de energia eficientes com uma gestão eficaz de energia podem ajudar muito na eficiência.

30

A nova arquitetura Kepler tem como objetivo não só criar a GPU mais rápida do mundo, mas também a GPU com maior desempenho e eficiência energética. Sua fabricação em 28nm é um dos importantes fatores pelo baixo consumo da nova arquitetura sem deixar de lado a alta performance. A economia de energia é algo que realmente impressiona na Kepler. Comparada à antecessora, a energia ativa foi reduzida em 15%, e o desperdício em 50%, o que resultou em uma melhora global de 35%. (LEPILOV E ZABELIN, 2012). O novo design do SM (Streaming Multiprocessor) que recebeu o nome de "SMX" foi especialmente redesenhado para obter a maior eficiência em performance por Watt, oferecendo 3x mais performance por Watt que sua antecessora, assim como demonstra a figura 2.

Figura 2: Novo design SM. Fonte: MORGAN, 2012

Estes novos avanços da tecnologia sobre as GPUs comprovam sua grande eficiência aliada ao baixo consumo de energia, geralmente isto também está associado a menor frequência de clock que elas possuem, porem o grande número de núcleos compensa esta perda e mais, até supera as CPUs no poder de processamento em certas operações. Como prova disso, temos o novo supercomputador da Petrobras que mudou seu paradigma e partiu para uma nova tecnologia. Este novo supercomputador usa nada menos que 180 GPUs e em geral

31 ele é muito mais rápido que o anterior e consome 90% menos energia, sem contar a falta de espaço que estavam sofrendo e também o supercomputador TITAN, que possui 10 vezes mais desempenho e 5 vezes mais eficiência energética que seu predecessor JAGUAR.

32

3 HARDWARE

O hardware é um dos principais temas abordados por este trabalho, por se tratar da peça fundamental e responsável pelo ganho de desempenho proposto. Portanto, este capítulo aborda as caraterísticas e peculiaridades de ambas arquiteturas estudadas.

3.1 CPU

A CPU (Unidade Central de Processamento), ou então processador como é mais conhecido pela comunidade em geral, é responsável por fazer a maior parte do processamento dos dados do computador e por isso é peça fundamental dos computadores. Por esses e outros fatores ele é considerado o componente mais complexo e frequentemente o mais caro de um computador. (MORIMOTO, 2007). Em 1965, Gordon Moore, um dos fundadores da Intel, afirmou que o número de transistores em um chip dobraria, sem custo adicional, a cada 18 meses. Tal afirmação ficou conhecida como a Lei de Moore, a qual foi válida durante anos, principalmente no final da década de 90. (ARRUDA, 2011). Conforme a tecnologia dos processadores foi evoluindo, o tamanho de seus transistores foi diminuindo de forma significativa. Porem, após o lançamento do Pentium 4, eles já estavam tão pequenos e numerosos que se tornou muito difícil aumentar o clock por limitações físicas, principalmente pelo superaquecimento gerado. (ARRUDA, 2011). A principal solução para esse problema veio com o uso de mais de um núcleo(core) ao mesmo tempo, através da tecnologia multicore. Assim, cada núcleo não precisa trabalhar numa frequência tão alta. Um processador dual-core de 1,5 GHz, por exemplo, poderia ter um desempenho semelhante a uma CPU de núcleo único de 3 GHz se o sistema de escalonamento fosse mais eficiente. Mas não parou por aí, a tecnologia continuou expandindo e hoje temos processadores com diversos núcleos em um único chip. Muito embora esta não fosse a primeira tecnologia de processamento paralelo em nível de hardware, ela causou uma verdadeira revolução no paralelismo. Devido a este avanço temos hoje supercomputadores com mais de um milhão de

33 núcleos de processamento, mas não necessariamente o mesmo número de processadores, oque seria inviável. Lançando-se então para o paralelismo em nível de hardware com núcleos de processamento diversos e em larga escala, temos as GPUs.

3.2 GPU

A própria NVIDIA define Unidade de Processamento Gráfico (GPU) como: “Processadores massivamente paralelos”. Você pode pensar na GPU como blocos de vários pequenos processadores trabalhando em paralelo, como pode ser observado na figura 3.

Figura 3: Diferença de cores entre uma CPU e uma GPU. Fonte: CAVERNADATI, 2011

Usadas, normalmente para cálculos gráficos, operações geométricas e operações de ponto flutuante, ou seja, processamento gráfico e jogos, em PCs, estações de trabalho, consoles de jogos. Mas somente a partir das séries 8, Tesla e Quadro da NVIDIA, e com a introdução da CUDA, as GPUs se tornaram programáveis, sendo capazes de executar aplicativos comuns e, com isso, através da modificação de algumas aplicações para algoritmos paralelos, conseguiram uma maior performance e escalabilidade dessas aplicações, resultando então em um alto poder de processamento designado principalmente a computadores de grande porte, ou seja, supercomputadores.

34

A filosofia do design das GPUs são significativamente diferentes, já que as placas de vídeo devem ser capazes de executar um número massivo de operações de ponto flutuante por frames de vídeo. Para isto, esses hardwares executam um número muito grande de threads simultanemante, enquanto um outro grande número de threads aguarda a latência de memória, minimizando assim o controle lógico de execução de tarefas. Ainda nessa arquitetura, pequenos cache de memória são utilizados para auxiliar o controle de memória, evitando que diferentes threads acessem várias vezes a memória DRAM. Consequentemente, mais áreas de chips são utilizadas para operações de ponto flutuante. (SANTOS, 2011).

Tudo isso se deu devido ao alto custo e esforço de desenvolvimento envolvidos na programação das GPUs, pois o hardware não era flexível e não se adaptava facilmente a evolução de novos algoritmos. Com o objetivo de atender a esta demanda por flexibilidade, surge a motivação para o uso de Stream Processors programáveis e dos Pixel Shaders. O modelo para computação com GPU é baseado em uma CPU e uma GPU juntas em um modelo de computação heterogêneo “co-processamento”. A parte sequencial do aplicativo é executado na CPU e na parte de computação intensiva é acelerada pela GPU. Do ponto de vista do usuário, o aplicativo apenas corre mais rápido, porque ele está usando a grande quantidade de SPs da GPU para aumentar o desempenho. Esta forma de uso das GPUs para propósitos gerais é chamada de GPGPU (General Purpose Computation on Graphics Processing Units). Desse modo, como a GPU é um processador vetorial massivamente paralelo onde é executada uma instrução em múltiplos conjuntos de dados, faz ela uso da classificação de Flynn, SIMD. Na figura 4 é possível observar uma comparação entre um chip de CPU e GPU, os núcleos ou cores os quais são tão mencionados, aqui estão ilustrados em quadrados verdes.

35

Figura 4: Comparação entre chips CPU e GPU. Fonte: IXBTLABS, 2008

No ano passado a Intel apresentou o seu Knights Corner, um co- processador que permite alcançar a marca de 1 teraflops - 1 trilhão de operações de ponto flutuante por segundo. O chip não é um processador comum, é um co- processador, cuja função é tirar do processador a incumbência de realizar os cálculos matemáticos mais intensivos. Em nota a Intel também publicou que “este novo chip parece ser o primeiro a fazer frente às GPUs, os processadores gráficos da NVIDIA, que estão dominando o mercado dos chamados supercomputadores de baixo custo”. O Knights Corner tem nada menos do que 50 núcleos em um único chip e, segundo a empresa, já incorpora a especificação PCI Express 3.0, que eleva a transferência de dados para 32 gigabytes por segundo. (INTEL, 2011).

3.2.1 GPGPU

GPGPU significa computação de proposito geral em unidades de processamento gráfico, também conhecida como computação em GPU. Uma vez projetadas especialmente para computação gráfica e difíceis de programar, as GPUs de hoje são processadores paralelos de uso geral com suporte para interfaces de programação e linguagens padrão da indústria. (GPGPU.ORG). Paralelismo dinâmico em GPUs Kepler, dinamicamente gera novas threads, adaptando-se os dados sem voltar para a CPU, simplificando e acelerando programação em GPU e acelerando um conjunto amplo de algoritmos populares. Uma plataforma multi-GPU representada por uma ou mais GPUs é capaz de realizar a computação heterogênea, aproveitando a capacidade de computação paralela dos múltiplos núcleos das GPUs para fornecer um aumento muito grande no desempenho com a complexidade de programação mínima. Enquanto aumenta

36 muito a capacidade funcional, a plataforma GPGPU também oferece o desempenho com tamanho muito menor, peso e potência. Isso resulta em economias significativas no custo, risco e tempo de colocação no mercado. (GE-IP.COM). A próxima geração de aceleradores gráficos de proposito geral está prestes a bater o mercado e mostram um novo foco em consumo de energia. Se é medido como GFLOPS / Watt ou picoJoules por instrução, e se a tecnologia está sendo aplicada a bateria óculos de visão noturna, grandes problemas exascale, a quantidade de energia consumida e a quantidade de calor a ser rejeitado é cada vez mais importante. A mudança de ênfase dos fornecedores de GPU reflete isso, e permitirá novas aplicações em ambas as extremidades do espectro do tamanho do sistema. (GE-IP.COM). Mike Houston da Universidade de Stanford abordou em seu trabalho o que aplicativos ideais para operarem com GPGPU devem ter:  Grandes conjuntos de dados.  Alto Paralelismo.  Dependências mínimas entre elementos de dados.  Intensidade aritmética alta.  Muito trabalho para fazer, sem intervenção da CPU.

Arnaldo Tavares, executivo responsável pela linha Tesla da NVIDIA na América do Sul, em uma palestra NVIDIA sobre processamento paralelo em GPUs na arquitetura Fermi, fez uma breve explicação sobre a GPU TESLA:

Tesla, são unidades de processamento gráfico (GPUs) da NVIDIA criadas especialmente para supercomputadores que rodam em alta performance e que necessitam de precisão extrema em seus resultados. Centros de pesquisas (medicina, biomecânica, astronomia), setor petrolífero e entidades financeiras, são instituições que necessitam da tecnologia para melhorarem o desempenho de seus trabalhos. (TAVARES, 2011).

37

Figura 5: NVIDIA TESLA M2090. Fonte: http://www.NVIDIA.in/object/why-choose-tesla-in.html

As GPUs são tão potentes por possuírem maior número de transistores para processamento que para caching de dados e controle de fluxo. Basicamente, a diferença fundamental entre as CPUs e as GPUs é o fato de que as CPUs são otimizadas para cálculos sequenciais, executando aplicativos diversos, enquanto as GPUs são otimizadas para cálculos massivamente paralelos, processando gráficos 3D. Os testes desta pesquisa foram realizados sobre uma NVIDIA GTX 465, a qual é otimizada para aplicações 3D. A linha de GPUs Tesla da NVIDIA é uma linha otimizada para GPGPU, estas GPUs são utilizadas principalmente em grandes servidores e supercomputadores. Esta linha possui um desempenho superior aos outros modelos, conforme a figura 6 publicada pela NVIDIA:

38

Figura 6: Desempenho NVIDIA Tesla. Fonte: http://www.NVIDIA.in/object/why-choose-tesla-in.html

3.2.2 Projeto Denver

O Projeto Denver é o codinome de uma iniciativa da NVIDIA que apresenta uma CPU NVIDIA executando um conjunto de instruções ARM, que será totalmente integrada no mesmo chip como a GPU NVIDIA. O Projeto Denver vai inaugurar uma nova era para a computação, alargando a gama de desempenho da arquitetura de instruções ARM, permitindo que a arquitetura ARM cubra uma maior parte do espaço da computação. Acoplada a uma GPU NVIDIA, que irá fornecer a plataforma de computação heterogênea do futuro, combinando uma arquitetura padrão com desempenho impressionante e eficiência energética. ARM já é a arquitetura padrão para dispositivos móveis. O Projeto Denver amplia a gama de sistemas ARM para cima de, PCs, servidores de data centers, e supercomputadores. Um anúncio da Microsoft revela estar trazendo o Windows para processadores de baixo consumo de energia, como CPUs baseadas em ARM, este era o ingrediente final necessário para permitir PCs com arquiteturas ARM. Um processador ARM juntamente com uma GPU NVIDIA representa a plataforma de computação do futuro. Uma CPU de alta performance com um conjunto de instruções padrão irá executar as partes seriais das aplicações e

39 fornecer compatibilidade enquanto uma GPU altamente eficiente e paralela irá executar as partes paralelas dos programas. O resultado é que os futuros sistemas vão integrar uma excepcional combinação de desempenho e eficiência de energia. Seus processadores irão fornecer o melhor dos dois mundos, permitindo maior vida útil da bateria para soluções móveis. (DALLY, 2011).

3.2.3 Evolução das Placas Gráficas NVIDIA

Na figura 7 é possível observar a evolução das GPUs durante os anos, estes chips gráficos começaram como processadores de vídeo com função fixa, mas se tornaram cada vez mais programáveis e poderosos em termos de computação, o que levou a NVIDIA a lançar a primeira GPU. No período entre 1999 e 2000, cientistas de computação e cientistas de campo em diversas áreas começaram a usar GPUs para acelerar uma variedade de aplicativos científicos. Esse foi o início do movimento chamado de GPGPU, ou Computação de Uso Geral na GPU. (NVIDIA).

FIGURA 7: Evolução Das GPUs. Fonte: CAVERNADATI, 2011

Jen-Hsun Huang CEO e co-fundador da NVIDIA em uma palestra em 2010, falou sobre o sucesso das GPUs, do CUDA e da importância da computação paralela. Além disso ele também demonstrou, através da figura 8, a evolução da arquitetura das placas gráficas da NVIDIA e o futuro da mesma.

40

Figura 8: NVIDIA GPU Roadmap. Fonte: http://www.geeks3d.com/20111126/NVIDIA-kepler-gpus-roadmap-gk107-gk106-gk104-gk110- and-gk112/

Atualmente estamos com a arquitetura Kepler, no próximo ano teremos a Maxwell, que conforme Jen-Hsun, vai acrescentar cerca de 16 vezes a performance de precisão dupla da Fermi. O ano de 2012 foi o ano da Kepler, com diversos lançamentos de GPUs, principalmente no segmento desktop (GK112, GK107, GK106, GK104) com chips produzidos a um processo de apenas 28nm e oferecendo mais de 1 TFLOPS de processamento. Em 2013 a nova arquitetura Maxwell promete uma arquitetura ainda menor, com apenas 22nm oque resultará em um ganho de performance de 16 vezes superior à da Kepler, 10-12 vezes mais veloz em aplicações “convencionais” e 40 vezes mais potente que a Fermi além de ser a primeira arquitetura a efetivamente usar o Projeto Denver. (HUANG, 2010). A capacidade de computação (Compute Capability) descreve os recursos suportados por uma GPU NVIDIA com suporte a CUDA. O primeiro hardware com suporte a CUDA, como a GeForce 8800 GTX, tem uma capacidade de computação (CC) de 1.0, já as mais recentes como a Tesla K20 tem um CC de 3.5, conforme a figura 9.

41

Figura 9: Tabela De Especificações. Fonte: http://www.pcper.com/reviews/Graphics-Cards/NVIDIA-Reveals-GK110-GPU-Kepler-71B- Transistors-15-SMX-Units

Impossível falar desta maravilhosa tecnologia sem citar um grande caso de sucesso. Relativamente há pouco tempo atrás a Petrobras abriu uma licitação para compra de equipamentos que serviriam para montar seu novo supercomputador e teve como vencedora a empresa ITAUTEC. (Petrobras monta supercomputador..., 2011). Luiz Rodolpho Rocha Monnerat, analista de sistemas sênior da Petrobras, que encabeçou o projeto, afirma que a nova tecnologia permitirá à Petrobras aumentar em dez vezes a capacidade de processamento de imagens de áreas com potencial de produção de gás e óleo. O upgrade se deu principalmente devido a falta de espaço no atual CPD e ao alto consumo de energia do antigo sistema. O novo supercomputador denominado Grifo4 é constituído por 544 servidores com um total de 1 petaflop de capacidade de processamento e ocupa a 68ª posição no ranking dos 500 melhores supercomputadores do mundo conforme o site Top500.org. Mas oque mais marcou na evolução do CPD da Petrobras foi a mudança do paradigma de processamento, integrando ao sistema nada menos que 180 GPUs NVIDIA Tesla 2050. Conforme Monnerat, o parque de computação de alto desempenho tem saltos tecnológicos a cada dez anos. A mudança atual é a substituição de CPUs pelas

42

GPUs. "Nossa meta é acompanhar esses saltos tecnológicos", afirma Monnerat. A Petrobras começou a testar GPUs em 2006. O primeiro cluster foi feito em 2008, com 180 placas gráficas. Bernardo Fortunato Costa, analista de sistemas júnior da Petrobras, afirma que os maiores problemas se deram devido aos softwares que ainda não eram adaptados para o processamento com as GPUs e que esse foi um dos fatores que levaram a companhia a desenvolver os programas internamente. Além do poder computacional aumentado pelo novo supercomputador, a mudança também se refletiu no consumo energético, o qual reduziu em 90%.

3.4 APU

Relativamente recente, o termo APU(Unidade de Processamento Acelerado) foi comentado inicialmente durante a Computex de 2010 e relembrado durante a durante a CES de 2011, onde a AMD demonstrou os novos chips e anunciou seu novo produto revolucionário de processamento paralelo batizado de Fusion. “Acreditamos que o Fusion é simplesmente o maior avanço em processamento desde a introdução da arquitetura x86, há mais de quarenta anos”, disse Rick Bergman, vice-presidente sênior e gerente geral do Grupo de Produtos da AMD, durante a CES de 2011. Segundo a detentora da marca, uma APU é um chip híbrido composto por uma CPU x86 e uma GPU, conforme a figura 10. Tal tecnologia permite um aumento na taxa de transmissão de dados entre ambos os dispositivos e uma expressiva diminuição no consumo de energia, o que é muito importante principalmente quando lembramos que o Fusion foi projetado inicialmente para portáteis, tais como notebooks.

43

Figura 10: Arquitetura de uma APU. Fonte: http://mecanicadocomputador.webnode.com.pt/album/galeria-de-fotos-pagina- inicial/processador-cpu-gpu-1-jpg/

Grande parte da experiência de computação está conectada ao software e, até agora, os desenvolvedores de software têm sido limitados pela natureza independente com a qual as CPUs e GPUs processam a informação. No entanto, as APUs AMD Fusion removem este obstáculo e viabilizam que os desenvolvedores aproveitem totalmente a capacidade de processamento incomparável de uma GPU — mais de 500 GFLOPs na A- Series “Llano” APU(v) — proporcionando uma performance do tipo de supercomputador para as tarefas de computação diárias. Mais aplicativos podem ser executados simultânea e mais rapidamente do que os designs anteriores da mesma categoria. (INÍCIO DA ERA DA APU..., 2011).

3.5 COMPARAÇÃO

O crescimento da frequência das CPUs é agora limitado por questões físicas e consumo elevado de energia. Seu desempenho é muitas vezes elevado pelo aumento do número de núcleos. Processadores atuais podem contem em torno de quatro núcleos, porem eles são projetados para aplicações comuns, baseados na arquitetura MIMD (múltiplas instruções sobre dados múltiplos). Cada núcleo funciona independentemente dos outros, executando várias instruções diferentes para os vários processos. Estes núcleos são projetados para executar um único segmento de instruções sequenciais, com velocidade máxima. Características especiais de vetor (SSE2 e SSE3) para vetores de ponto flutuante simples e de precisão dupla apareceram em processadores de propósito

44 geral por causa do aumento das exigências de aplicações gráficas em primeiro lugar. É por isso que é mais conveniente usar GPUs para determinadas tarefas. Além das centenas de núcleos das GPUs, elas contêm uma memória global rápida, que pode ser acessada por todos os multiprocessadores, memória local em cada multiprocessador e memória especial para constantes. O mais importante é que esses vários núcleos em uma GPU são SIMD (Uma instrução sobre múltiplos dados). E esses núcleos de executam as mesmas instruções simultaneamente, pois foram projetados para a execução rápida de várias threads de instrução paralelas. Este estilo de programação é comum para algoritmos gráficos e muitas tarefas científicas. (IXBTLABS, 2009). Também vale lembra que o crescimento anual da tecnologia da arquitetura das CPUs é de apenas 40% se comparado as GPUs que é de 130%.

45

4 SOFTWARE

Apenas o hardware por si só não é capaz de nos fornecer o desempenho esperado, portanto, para a arquitetura ser completa ela é dependente de um software que demande destes recursos de hardware, mas acima disso, o software deve tratar deste ambiente de hardware de forma a extrair o máximo de desempenho dele, fazendo uso de todos seu recursos.

4.1 LINGUAGENS DE PROGRAMAÇÃO

Esta seção foca única e exclusivamente no nível de linguagens de programação, por se tratarem da base para se criar um software. A linguagem de programação a ser utilizada em uma aplicação de alto desempenho é um fator crucial, pois assim como o hardware, a linguagem também deve proporcionar muito desempenho, como a linguagem C, que fornece uma iteração próxima ao hardware.

4.1.1 C/C++

A linguagem C foi criada por Dennis Ritchie, em 1972, no centro de pesquisas da Bell Laboratories, é uma das linguagens de programação mais populares e existem poucas arquiteturas para as quais não existem compiladores. Sua primeira utilização importante foi a reescrita do Sistema Operacional UNIX, que até então era escrito em assembly. C é uma linguagem de programação compilada, estruturada, imperativa, procedural, de propósito geral e padronizada pela ISO. C foi útil para muitas aplicações que foram codificadas originalmente em Assembly, fornecendo acesso de baixo nível a memória e ao hardware em geral, mas também foi desenvolvido para ser uma linguagem de alto nível, para maior reaproveitamento do código. A linguagem C++, além das características do próprio C, ela é considerada uma linguagem de nível médio, pois combina características de linguagens de alto e baixo nível. É uma das linguagens comerciais mais populares, sendo bastante usada também na academia por seu grande desempenho e base de utilizadores.

46

O C++ foi desenvolvido como um adicional a linguagem C por Bjarne Stroustrup em 1983 no Bell Labs. Novas características foram adicionadas com o tempo, como funções virtuais, sobrecarga de operadores, herança múltipla, gabaritos, tratamento de exceções e também implementou vários elementos chave de programação orientada a objetos.

4.1.2 Java

O Java, além de uma linguagem de programação de alto nível, também é uma plataforma. Linguagem orientada a objeto, com sintaxe parecida ao C, desenvolvida na década de 90 por uma equipe de programadores chefiada por James Gosling, na empresa Sun Microsystems, mas atualmente é mantida pela Oracle. Na linguagem de programação Java, todo código fonte é primeiramente escrito em arquivos de texto simples que terminam com a extensão .java. Estes arquivos por sua vez são compilados em arquivos .class pelo compilador javac. Uma arquivo .class não contém código que é nativo para o processador, ao invés disso ele contém bytecodes que por sua vez é a linguagem de máquina da Java Virtual Machine, como pode ser observado na figura 11. A ferramenta de execução Java então executa sua aplicação com uma instância da máquina virtual Java. (ORACLE). É este arquivo pré-compilado .class com os bytecodes que fornece a portabilidade ao Java. Este mesmo arquivo pode ser executado tanto em um ambiente Windows quanto Linux ou Mac, como demonstra a figura 12.

Figura 11: Plataforma Java. Fonte: http://docs.oracle.com/javase/tutorial/getStarted/intro/definition.html

47

Como um ambiente independente de plataforma, a plataforma Java pode ser um pouco mais lenta do que o código nativo. No entanto, os avanços no compilador e tecnologias de máquinas virtuais estão trazendo mais perto o desempenho do código nativo sem ameaçar a portabilidade.

Figura 12: HelloWord Java. Fonte: http://docs.oracle.com/javase/tutorial/getStarted/intro/definition.html

4.2 DESENVOLVIMENTO PARA GPU

Desenvolver uma aplicação voltada a utilizar os recursos de uma GPU é uma tarefa árdua, por se tratar de um paradigma diferente que requer muita atenção e trabalho ao utilizar o hardware. Nestes casos geralmente são utilizadas APIs as quais fornecem um desenvolvimento de alto nível ao hardware, como são os casos do CUDA e da APARAPI.

4.2.1 CUDA

48

A NVIDIA, detentora da marca, define CUDA (Compute Unified Device Architecture) como uma arquitetura de computação paralela de propósito geral que tira proveito do mecanismo de computação paralela das unidades de processamento gráfico (GPUs) NVIDIA para resolver muitos problemas computacionais complexos em uma fração do tempo necessário em uma CPU. Ela é uma extensão para a linguagem de programação C, desenvolvida pela NVIDIA, que possibilita o uso de computação paralela. A ideia por trás disso tudo é que programadores possam usar os poderes da unidade de processamento gráfico (GPU) para realizar algumas operações mais rapidamente. Ou seja, a GPU passa a operar como se fosse mais uma CPU dentro máquina, aumentando consideravelmente, com isso, a performance do sistema. A CUDA também é conhecida como a arquitetura de computação paralela da NVIDIA que também já há algum tempo tem sido aclamada como "A Supercomputação para as Massas". Esta API é muito famosa entre os acadêmicos da computação e chegou também a ser usada no programa Exascale, do Departamento de Defesa dos Estados Unidos. Mas não é só o desempenho o responsável por tanta popularidade: o preço dá o golpe final nessa luta. Com todo o seu poder de computação, as placas gráficas são incrivelmente baratas. Como observou Sharon Glotzer, da Universidade de Michigan,: "Hoje você pode ter dois gigaflops por US$500. Isso é ridículo." (PFISTER, 2010). Segundo a NVIDIA, a CUDA permite utilizar recursos dos aceleradores NVIDIA utilizando chamadas em C (C for CUDA, compilador nvcc), o que torna o processo como um todo relativamente fácil para os BONS PROGRAMADORES. Existem também abstrações para a linguagem Java, C# e também Python. O processamento paralelo da GPU permite executar mais ações com menos tráfego de informações em barramentos, usando a área de cache comum e acesso direto a memória. Segundo a NVIDIA, com a utilização da API OpenMM na química computacional que desenvolve cálculos moleculares, é possível desenvolver estes cálculos de uma forma mais simples e, principalmente, integrada com as GPUs. Logo o trabalho que uma CPU processaria em dias, poderá ser processado em horas, ou até mesmo minutos, conforme as figuras 13, 14 e 15.

49

Figura 13: Comparação de desempenho. Fonte: http://www.NVIDIA.com/object/citizenship-report-science-medicine.html

A GPU se torna mais apta para o trabalho de processamento paralelo por ter sido desenvolvida para atender à demanda de processos de computação 3D em alta resolução e em tempo real. Assim, com o passar do tempo, as GPUs modernas se tornaram muito eficientes ao manipular grandes blocos de informações (O QUE É A TECNOLOGIA..., 2011).

O processamento na GPU é feito dentro dos núcleos CUDA (CUDA Cores), que podem ser comparados, a grosso modo, com os núcleos de um processador comum. Por isso, quanto mais núcleos CUDA tiver a placa de vídeo, mais operações podem ser realizadas em paralelo (O QUE É A TECNOLOGIA..., 2011).

Na questão da Dinâmica de Fluídos, a NVIDIA divulga vários projetos em andamento sobre modelos de Navier-Stokes e métodos de Lattice Boltzman, os quais mostraram grandes ganhos de velocidade usando GPUs habilitadas para CUDA.

50

Figura 14: Modelo de Navier-Stokes. Fonte: http://www.NVIDIA.com/object/computational_fluid_dynamics.html

Figura 15: Método de Lattice Boltzman. Fonte: http://www.NVIDIA.com/object/computational_fluid_dynamics.html

O modelo de programação CUDA implica em agrupar as threads em grupos que são chamados de blocks que comunicam-se entre si através da memória compartilhada, os blocks por sua vez também são agrupados e chamados de grids. Um programa (kernel) é executado através de uma grid de blocks de threads, conforme a figura 16. Uma grid é executada de cada vez. Cada bloco pode ser de uma, duas, ou três dimensões, de forma, que pode consistir de 1024 threads

51 dependendo do equipamento, como demonstra a figura 16. (GRAMMELSBACHER E MEDRADO, 2009).

Figura 16: Modelo de Programação CUDA. Fonte: IXBTLABS, 2008

Blocos de threads são executados sob a forma de pequenos grupos chamados warps. É o volume mínimo de dados, que podem ser processados por multiprocessadores. Mas o CUDA também permite trabalhar com blocos contendo outros números de threads. (IXBTLABS, 2008). O modelo de memória CUDA permite endereçamento bytewise com suporte para reunir e dispersar. Cada stream processor possui até 1024 registradores. O acesso a esses registradores é muito rápido, eles podem armazenar 32 bits de números inteiros ou de ponto flutuante. Assim como mostra a figura 17, cada thread tem acesso aos seguintes tipos de memória:

52

Figura 17: Modelo de Memória CUDA. Fonte: IXBTLABS, 2008

Memória global - o maior volume de memória disponível para todos os multiprocessadores em uma GPU, com mais de 6 GB em soluções empresariais modernas. Ela oferece alta largura de banda, mais de 170 GB/s de soluções top da NVIDIA. Memória local - volume pequeno de memória, que pode ser acessado apenas por um stream processor. É relativamente lento, assim como a memória global. Memória compartilhada - de até 48 Kb de memória, nos melhores modelos da NVIDIA, compartilhado entre todos os stream processors em um multiprocessador. É rápida, assim como registradores. Esta memória fornece a interação entre as threads, é controlada pelos desenvolvedores diretamente e apresenta baixas latências, ela pode ser usada como uma cache L1 controlável, menos chamadas para memória global. Armazenamento constante – pouco maior que a memória compartilhada, ela é somente leitura para todos os multiprocessadores, bastante lenta.

53

Memória de textura - está disponível para a leitura de todos os multiprocessadores. Os dados são obtidos por unidades de textura em uma GPU, de modo que os dados podem ser interpolados linearmente, sem custos extras, lenta como a memória global.

Figura 18: Memórias CUDA. Fonte: IXBTLABS, 2008

Na figura 18, é possível observar que CUDA implica em uma abordagem especial para o desenvolvimento, um pouco diferente da programação da CPU. É preciso ter a consciência de tipos de memória diferentes, o fato de que a memória local e global não está em cache e que suas latências de acesso são muito mais elevadas do que na memória de registrador, como é fisicamente localizado em chips separados. Vale ressaltar que estes números citados neste tópico são exclusivos da GPU e estão sujeitos a variações conforme o modelo. Segue um modelo típico de programação CUDA, mas não obrigatório:  Dividir uma tarefa em subtarefas.

 Dividir os dados de entrada em pedaços que se encaixam na memória compartilhada.  Cada bloco de dados é processado por um thread block.

54

 Carregar parte dos dados da memória global na memória compartilhada.  Dados de processamento na memória compartilhada.

 Copiar resultados da memória compartilhada de volta à memória global. Algumas restrições do modelo CUDA (JEONG, 2008):  Não permite recursão.  Não se pode declarar variáveis estáticas dentro do corpo da função.  Não se pode ter um número variável de argumentos.  Não se pode ter ponteiro de função.  Parâmetro de função para uma função kernel é limitado a 256 bytes.

4.2.2 CUDA 5

O CUDA 5 traz diversas novas funcionalidades e aperfeiçoamentos para as que já existiam, como:  Dynamic Parallelism;  GPU Object Linking;  New Nsight Eclipse Edition;  GPUDirect;

O Paralelismo Dinâmico é a capacidade para qualquer thread de GPU poder lançar um kernel paralelo nela mesma, de forma dinâmica, simultânea e independente. Na arquitetura Fermi, apenas a CPU poderia gerar trabalho para a GPU. A partir da arquitetura Kepler, a GPU pode gerar trabalho para ela mesma, criando um novo kernel sempre que necessário, como pode ser observado nas figuras 19 e 20. (HARRIS).

55

FIGURA 19: Paralelismo Dinamico. Fonte: http://www.NVIDIA.com/object/NVIDIA-kepler.html

Figura 20: Mandelbrot. Fonte: http://techreport.com/review/22989/a-brief-look-at-NVIDIA-gk110-graphics-chip/2

Talvez a melhor ilustração desta capacidade seja o caso de computação clássica de avaliação de uma imagem fractal como o conjunto de Mandelbrot. Na GK110, uma rotina de Mandelbrot poderia avaliar a área de imagem, quebrando-a em uma malha grossa e verificando para ver quais partes desta grade contem uma borda. Os blocos que não contêm uma borda não teriam de serem avaliados

56 posteriormente, e o programa pode ampliar as zonas com borda para calcular a sua forma mais detalhadamente. O programa pode repetir este processo várias vezes, de cada vez ignorando blocos sem borda e focando mais em blocos com bordas em si mesmas, de modo a conseguir um resultado de altíssima resolução sem executar o trabalho e sem ser necessário ficar constantemente retornando para a CPU para orientação. (TECHREPORT). GPUDirect é o recurso que permite remotamente acesso direto a memória entre qualquer GPU do cluster. A versão inicial do GPUDirect suporta comunicação acelerada com dispositivos de rede e de armazenamento, mas continuou a evoluir, adicionando suporte para a comunicação peer-to-peer entre GPUs e APIs otimizado para soluções de vídeo em 2011 e RDMA entre GPUs com a versão 5 do CUDA em 2012. (NVIDIA).

4.2.3 APARAPI

A Parallel API, ou simplesmente APARAPI é uma biblioteca que vem sendo desenvolvida pela AMD há relativamente pouco tempo. Segundo a detentora, a biblioteca possui código fonte aberto e seus fins são diversos. A AMD está revendo as necessidades dos programadores Java, tratando de fazer com o APARAPI uma implementação contraria e muito diferenciada das existentes, pois a APARAPI não procura mudar a forma de trabalho nem os métodos de programação dos desenvolvedores Java, mas adaptar-se a eles. A APARAPI pode ser usada por desenvolvedores Java para desenvolver aplicações que serão executadas diretamente por uma GPU e/ou uma APU sem precisar escrever uma linha de ATI Stream, OpenCL ou CUDA. Ela reforça o lema do Java, “Write Once, Run Anywhere”, que significa “Escreva uma vez, execute em qualquer lugar”, porem com o diferencial de que sua aplicação portável quase sempre poderá fazer uso de todos recursos de processamento em qualquer computador que seja executado, tendo ele uma GPU ou não. A biblioteca é capaz de converter de forma transparente em tempo de execução o bytecode Java em um código compatível com o OpenCL, compatível com os mais diversos modelos de GPU. Desta forma permite que os desenvolvedores de aplicativos e plugins Java aproveitem o poder computacional das GPUs e APUs sem demasiadas complicações. Também de forma transparente, a APARAPI, analisa o código e

57 verifica a disponibilidade de uma GPU com suporte ao OpenCL, se não estiver disponível no computador, a APARAPI automaticamente voltará a execução usando um pool de threads Java, fazendo com que a aplicação rode em múltiplas CPUs obtendo o máximo de desempenho possível. (GEADA, 2012).

Recursos oferecidos pela APARAPI:  Não há necessidade de pensar em transferências de dados explícitos entre o host do programa e o kernel GPU (Se necessário você pode fazer para os fins de melhorias).  Não há necessidade de consultar os dispositivos e a plataforma.  Não há necessidade de criação de contexto e gerenciamento de fila de comando.  Não há necessidade de escrever código para a criação de buffers.  Não há necessidade de definir explicitamente os argumentos do kernel.  Não há necessidade de definir parâmetros como os globalThreads e localThreads e dimensões.  Se a GPU não é encontrada, a APARAPI transforma o código do kernel para usar JTP (Java Pool Thread) ou a CPU. Você não é obrigado a verificar explicitamente se existe GPU ou não e não há necessidade de criar explicitamente tópicos para gerenciar o paralelismo de dados.  Não há necessidade de aprender OpenCL C para escrever aplicações altamente paralelas de dados.  Curva de aprendizagem rápida.  APARAPI é um projeto Open Source.

A APARAPI está tendo uma série de vantagens, porem existem algumas limitações:  Não há suporte para a memória local (isto causa uma enorme perda de desempenho).  Pode não alcançar o desempenho altamente otimizado por OpenCL C para operações matemáticas complexas.

58

4.2.4 OpenCL

OpenCL é um padrão aberto e livre de royalties para a programação paralela em ambientes computacionais heterogêneos, compostos de CPUs, GPUs, APUs e outros processadores, como mostra a figura 21. OpenCL permite a escrita de código multi-plataforma para execução nestes dispositivos, possibilitando a utilização de todo o poder computacional disponível no ambiente.

Figura 21: OpenCL. Fonte: http://forums.macrumors.com/showthread.php?t=613836

O OpenCL é uma espécie de primo-irmão do OpenGL, que apesar de não ser mais tão usado em jogos (perdendo espaço para o DirectX) é o mais usado em aplicações profissionais. Ambos são desenvolvidos pelo “Khronos Group”, uma associação de fabricantes destinada a desenvolver padrões abertos. Ele permite não apenas o uso de GPUs, CPUs e APUs, mas também de outros chips aceleradores (como processadores Cell) o que o torna uma opção teoricamente universal. A partir da versão 1.1 da especificação do OpenCL, o modelo de programação OpenCL é híbrido de paralelismo de tarefas e paralelismo de dados. Para tirar vantagem da arquitetura do OpenCL, dois modelos de programação

59 podem ser utilizados. O primeiro é o modelo de programação de dados paralelos e a outra é o modelo de programação de tarefas paralelas. No modelo de programação de dados em paralelo, cada kernel executa o mesmo bloco de código, mas em dados diferentes. O programador pode escolher particionar manualmente os kernels em grupos, ou delegar essa tarefa para o middleware do OpenCL. Além do modelo de programação paralela de dados, o programador pode optar por usar o modelo de programação de tarefas paralelas. Neste caso, cada elemento de processamento vai executar um núcleo de computação separada, e paralelismo é alcançado através da execução de múltiplos núcleos separados work-items. (COMPUTER SCIENCE, 2011).

60

5 AMBIENTE DE TESTES

Para se comprovar a eficiência da arquitetura estudada, foi mantado um ambiente de testes, o qual compreende hardware e software que estão descritos nas seções seguintes.

5.1 HARDWARE

Para os testes realizados na arquitetura, foi utilizado um computador pessoal com os seguintes itens de hardware descritos na tabela 2:

Tabela 2: Hardware Item Descrição Quantidade Processador: Intel Core I7 920 2.66Ghz 8Mb 1 1 Cache 4.8GT/s 4 Cores 8 Threads

2 Placa-mãe: XFX X58i 1 3 Memória RAM: 1Gb DDR3 1333Mhz 3 Placa de Vídeo: NVIDIA GeForce GTX465 EVGA 256bit 1Gb GDDR5, 102.6 Gb/s, PCI-

4 Exp 2.0 16X, Processor Clock 1215Mhz 1 Memory Clock 1603Mhz, Graphics Clock 607Mhz

5 Fonte: Corsair 650TX 650Watts 80Plus 1 Disco Rígido: Western Digital Caviar Black 6 1 1Tb 64mb 7200rpm SATA 3 Disco Rígido: Samsung 160Gb 7200rpm 7 2 SATA2 (RAID 0)

Detalhes técnicos da GPU utilizada na arquitetura e exibida na figura 22, relevantes ao processamento paralelo estão descritos na tabela 3:

Tabela 3: Detalhes da GPU Compute Capability 2.0 Dimensão das Grids de Blocos de threads 3

61

Dimensão do Bloco de threads 3 Dimensão dos Blocos 1024 Quantidade de Blocos 32 CUDA cores 352 Compute Performance 30x Versão OpenGL 4.2

Figura 22: GTX 465 Fonte: 3DVISION-BLOG, 2010

5.2 SOFTWARE

Para a realização dos testes de benchmark, foram utilizados os seguintes softwares descritos na tabela 4:

Tabela 4: Software Item Descrição Licença 1 Compilador para a linguagem C: gcc 4.7.2 Freeware 2 API CUDA Toolkit 5 Freeware 3 Biblioteca APARAPI (06/05/2012) Freeware 4 Driver de vídeo NVIDIA 306.97 Freeware 5 Java JRE e JDK 6 Freeware 6 IDE de desenvolvimento Java: Netbeans 7.2.1 LGPL/GPL 7 Sistema Operacional: Linux Mint 13 Kernel 3.2 GPL

62

5.3 Benchmarks

Para comprovar a eficiência das GPUs, foram realizados uma série de testes com algoritmos de benchmarks executando cálculos na GPU. Da mesma forma foram executados os algoritmos de benchmark na CPU a fim de comparar o desempenho de ambas. As codificações dos algoritmos e os trechos de códigos mais relevantes se encontram em anexo, no final do trabalho. O CUDA Toolkit instalado para realizar os testes, também acompanha diversos exemplos de aplicações que fazem uso do CUDA. Destes exemplos, foram utilizados o algoritmo do Conjunto de Mandelbrot, a simulação de corpos para testes espaciais e a multiplicação de matrizes com CUBLAS que possui um trecho de seu código no ANEXO A4. Também foram desenvolvidos algum algoritmo de benchmark como busca de números perfeitos, números primos e multiplicação de matrizes, tanto com CUDA quanto com APARAPI.

5.3.1 O Conjunto de Mandelbrot

O conjunto de Mandelbrot é um fractal definido pela seguinte equação:

Um fractal é uma forma geométrica que pode ser dividida em diversas partes de si mesma e ainda assim continuar semelhante ao formato original, como pode ser observado na figura 23:

Figura 23: Conjunto de Mandelbrot Fonte: MAZZON, 2012

63

5.3.2 Simulação de Corpos

Uma simulação de corpos numericamente aproxima a evolução de um sistema de corpos nos quais cada organismo interage continuamente com qualquer outro corpo. Um exemplo familiar é uma simulação astrofísica em que cada corpo representa uma galáxia ou uma estrela individual, e os corpos atraem-se mutuamente através da força da gravidade. Alguns exemplos podem ser observados na figura 24.

Figura 24: Simulação de Corpos. Fonte: NYLAND

64

6 RESULTADOS

Os resultados coletados nos testes foram com base na execução dos algoritmos de benchmark, citados nos ANEXOS, em ambas as arquiteturas. Cada algoritmo faz uso de paralelismo e explora de igual o melhor da arquitetura. Cada resultado de cada teste foi obtido através de uma média de 10 execuções sobre as mesmas condições. Estes resultados foram agrupados e exibidos através de gráficos comparativos nas seções seguintes.

6.1 CUDA

Para os testes realizados sobre a API CUDA, cada algoritmo foi otimizado em questão de quantidade de threads visando aperfeiçoar a eficiência em ambas as arquiteturas e para uma comparação mais justa. Na GPU foram criados 32 blocos com 1024 threads cada, já na CPU foram criadas 20 threads.

6.1.1 Cálculo de Números Perfeitos

Este teste se dá pelo fator número máximo calculado pelo tempo consumido em segundos para reproduzir a simulação. Onde quanto menor o tempo gasto, melhor. Os algoritmos destes testes podem ser observados nos anexos ANEXO A, utilizando CUDA, e ANEXO A1, usado pthread.

Cálculo de Números Perfeitos

2500 2000 1500 1000 500

Tempo(segundos) 0 100000 500000 1000000 2000000 GPU 0,2 3,6 14 61 CPU 1,4 34 132 531 Seq 5,6 129 515 2110 SpeedUp 28 35,83 36,79 34,59 Número máximo calculado / Tempo

65

6.1.2 Cálculo de Números Primos

Este teste se dá pelo fator número máximo calculado pelo tempo consumido em segundos para reproduzir a simulação. Onde quanto menor o tempo gasto, melhor. Os algoritmos destes testes podem ser observados nos anexos ANEXO A2, utilizando CUDA, e ANEXO A3, usado pthread.

Cálculo de Números Primos 700

600

500 400 300

200 Tempo(segundos) 100 0 100000 500000 1000000 2000000 GPU 0,4 8,2 32 133 CPU 0,6 12 45 165 Seq 2,3 42 175 590 SpeedUp 5,75 5,12 5,47 4,44 Número máximo calculado / Tempo

6.1.3 Multiplicação de Matrizes

Este teste se dá pelo fator dimensão da matriz pelo tempo consumido em segundos para reproduzir a simulação. Onde quanto menor o tempo gasto, melhor. O algoritmo deste teste pode ser observado no ANEXO A4, utilizando CUDA e a biblioteca CUBLAS. Este teste foi extraído dos exemplos que acompanham o CUDA Toolkit.

66

Multiplicação de Matrizes 1600

1400

1200 1000 800 600

400 Tempo(segundos) 200 0 640x640 1280x1280 1600x1600 3200x3200 GPU 0,4 0,7 1 4,7 CPU 9,6 45 92 394 Seq 36 168 351 1490 SpeedUp 90 240 351 317,02 Dimensão da matriz / Tempo

6.1.4 Conjunto de Mandelbrot

Este teste se dá pelo fator detalhamento da imagem pelos FPS produzidos. Onde quanto mais FPS melhor. Este teste foi extraído dos exemplos que acompanham o CUDA Toolkit.

Conjunto de Mandelbrot 120

100

80

60 FPS

40

20

0 256 512 1024 2048 4096 8192 16384 GPU 60 60 60 110 70 30 14 CPU 3,2 1,7 0,9 0,4 0,2 0,1 0,01 Seq 0,8 0,425 0,225 0,1 0,05 0,025 0,0025 Detalhamento da imagem / FPS

67

6.1.5 Simulação de Corpos

Este teste se dá pelo fator quantidade de corpos pelo tempo consumido em milissegundos para reproduzir a simulação. Onde quanto menor o tempo gasto, melhor. Este teste foi extraído dos exemplos que acompanham o CUDA Toolkit.

Simulação de Corpos 2500000,00

2000000,00

1500000,00

1000000,00

Tempo(milissegundos) 500000,00

0,00 1000 5000 10000 20000 50000 GPU 0,63 13,39 54,20 213,13 1181,44 CPU 209,67 5246,95 20975,89 83927,92 524653,13 Seq 838,70 20987,78 83903,56 335711,69 2098612,50 SpeedUp 1333,38 1568,01 1548,06 1575,12 1776,32 Número de Corpos / Tempo

6.2 APARAPI

6.2.1 Cálculo de Números Perfeitos

Este teste se dá pelo fator número máximo calculado pelo tempo consumido em segundos para reproduzir a simulação. Onde quanto menor o tempo gasto, melhor. O algoritmo deste teste pode ser observado no ANEXO B.

68

Cálculo de Números Perfeitos 2500

2000

1500

1000 Tempo (segundos)Tempo 500

0 10000 100000 500000 1000000 GPU 0,2 0,5 7,7 29 CPU 0,007 5,1 126 513 Seq 0,025 18 480 1932 SpeedUp 0,13 36 62,34 66,62 Número máximo calculado / Tempo

6.2.2 Cálculo de Números Primos

Este teste se dá pelo fator número máximo calculado pelo tempo consumido em segundos para reproduzir a simulação. Onde quanto menor o tempo gasto, melhor. O algoritmo deste teste pode ser observado no ANEXO B1.

Cálculo de Números Primos 2500

2000

1500

1000

Tempo(segundos) 500

0 10000 100000 500000 1000000 GPU 0,2 0,5 8,2 32 CPU 0,007 5,2 130 521 Seq 0,025 20 495 1958 SpeedUp 0,13 40 60,37 61,19 Número máximo calculado / Tempo

69

6.2.3 Multiplicação de Matrizes

Este teste se dá pelo fator dimensão da matriz pelo tempo consumido em segundos para reproduzir a simulação. Onde quanto menor o tempo gasto, melhor. O algoritmo deste teste pode ser observado no ANEXO B2.

Multiplicação de Matrizes 450 400 350 300 250 200 150

Tempo (segundos) Tempo 100 50 0 640x640 1280x1280 1600x1600 3200x3200 GPU 0,4 2,2 2,8 26 CPU 7,1 23,7 25,8 117 Seq 28,4 82 89 405 SpeedUp 71 37,27 31,79 15,58 Dimensão da matriz / Tempo

6.2.4 Conjunto de Mandelbrot

Este teste se dá pelo fator dimensão da imagem gerada, pelos FPS produzidos. Onde quanto mais FPS, melhor. Este teste foi extraído dos exemplos que acompanham a APARAPI.

70

Conjunto de Mandelbrot 1000 900 800 700

600

500 FPS 400 300 200 100 0 256x256 512x512 1024x1024 2048x2048 GPU 930 420 136 31 CPU 24 20 9 2 Seq 7 6 3 0,5 Dimensão da imagem / FPS

6.3 Comparação entre as APIs

6.3.1 Cálculo de Números Perfeitos

Números Perfeitos 35 30 25 20 15 10

Tempo (segundos)Tempo 5 0 100000 500000 1000000 CUDA 0,2 3,6 14 APAR 0,5 7,7 29 Número máximo calculado / Tempo

71

6.3.2 Cálculo de Números Primos

Números Primos 35 30 25 20 15 10

Tempo (segundos)Tempo 5 0 100000 500000 1000000 CUDA 0,4 8,2 32 APAR 0,5 8,2 32 Número máximo calculado / Tempo

6.3.3 Multiplicação de Matrizes

Multiplicação de Matrizes 30 25 20 15 10

Tempo(segundos) 5 0 640x640 1280x1280 1600x1600 3200x3200 CUDA 0,4 0,7 1 4,7 APAR 0,4 2,2 2,8 26 Dimensão da Matriz / Tempo

72

CONCLUSÃO

O aumento de velocidade dramático do núcleo computacional fornece uma forte motivação para o trabalho contínuo em paralelismo com GPGPU. Evidentemente, a paralelização com GPU tem um efeito dramático sobre o tempo total de execução, reduzindo-o por um fator de mais de 10 vezes que por sua vez comparado a energia dissipada pela GPU em questão que é de no máximo 200W com a energia dissipada pela CPU que chega a 130W, teoricamente ela mostra-se uma melhor relação consumo/desempenho. O novo CUDA 5 se mostrou muito mais prático e fácil de usar, desde a instalação ao desenvolvimento simplificado, sem contar os diversos exemplos que acompanham o SDK. Com relação ao CUDA, a APARAPI se mostrou bem promissora, apesar de ser relativamente nova quanto ao CUDA, ela fornece aos desenvolvedores Java a possibilidade de paralelizar tarefas na GPU, oque antes era inviável na linguagem. Comparando-se a performance da APARAPI com CUDA, é possível afirmar que os resultados obtidos foram satisfatórios, pois apesar da performance ter sido um pouco menor, deve-se levar em conta que esta está executando sobre uma JVM que consequentemente gera uma perda de desempenho na aplicação, pois não é uma aplicação nativa rodando diretamente sobre o sistema operacional. A transição de nível de sistema para plataformas de computação exóticas é sempre uma troca de engenharia. Os benefícios de desempenho de um ambiente como esse devem ser suficientemente altos para compensar o custo de desenvolvimento e manutenção com o novo sistema. A plataforma GPGPU e seu ambiente de programação é suficientemente familiar para um programador e expõe a massiva capacidade paralela de uma forma simples. O aumento de desempenho imediato é evidente a partir dos benchmarks preliminares apresentados neste relatório. Significativas otimizações adicionais podem ser realizados em trabalhos futuros através de mais análise e refinamento desta abordagem GPGPU. Empresas de hardware estão trabalhando duro para fornecer sistemas sólidos acessíveis baseados em GPU, projetados especificamente para computação de propósito geral, e há uma grande quantidade de software disponível para aproveitar as oportunidades oferecidas por esses dispositivos. GPGPUs fornecerem soluções para desafios do mundo real, a partir de renderização de vídeo e alto

73 desempenho em jogos de computação matemática e análise numérica (GPGPU COMPUTING..., 2010). Por fim, ainda foi realizada uma comparação entre as APIs CUDA e APARAPI sobre os resultados obtidos nos testes anteriores. Ambas tem uma relação muito próxima e apesar da APARAPI ser uma biblioteca para aplicativos Java, ela converte o código da GPU para código OpenCL, o que também torna muito flexível e com uma melhor performance, o que levou a obter resultados muito próximos ao CUDA. Portanto, com certeza a APARAPI já é uma boa alternativa aos desenvolvedores Java, podendo facilmente tornar uma simples aplicação altamente escalável. Em trabalhos futuros uma arquitetura alternativa poderia firmemente unir processos de CPU com processos de GPU para maximizar a utilização do sistema. A implementação do kernel da GPU pode ser mais trabalhado conforme o contexto do problema, esta prática otimizaria ainda mais a performance de GPU. Recomendável também que em trabalhos futuros seja utilizada uma GPU com maior capacidade computacional e principalmente que seja de uma série destinada ao GPGPU assim como a série TESLA, para que possam serem extraídos mais recursos da arquitetura e da própria GPU. Outra alternativa para trabalhos futuros é fazer uma analise do real consumo de energia de ambas arquiteturas para calcular melhor o custo benefício. Esta analise demonstraria com precisão os dados do consumo para comprovar, ou não, a melhor eficiência energética das GPUs.

74

REFERÊNCIAS BIBLIOGRAFICAS ABOUT GPGPU.org. Disponível Em: . Acesso em: 09 jul. 2012.

ABOUT the Java Technology. Disponível Em: . Acesso em: 05 set. 2012.

AMD Accelerated Processing Units. Traduzido por: Google Tradutor. Disponível em: . Acesso em: 26 mar. 2012.

AMD apresenta o Fusion na CES de 2011. Disponível em: . Acesso em 26 mar. 2012.

ARRUDA, Felipe. A história dos processadores. 2011. Disponível Em: . Acesso em: 15 set. 2011.

CARTEY, Luke. Domain Specic Languages for Massively Parallel Processors PRS Transfer Report. 2010. Disponível Em: . Acesso em: 18 ago. 2012.

COMPUTER Science. CSC/ECE 506 Spring 2011/ch2a mc. 2011. Disponível Em: . Acesso em: 22 jul. 2012.

CLUSTERS e Supercomputação. Disponível em: . Acesso em: 26 mar. 2012.

CUDA PROGRAMAÇÃO Paralela Facilitada. Disponível em: . Acesso em: 29 mar. 2012.

CUDA PROGRAME a sua NVIDIA. Disponível em: . Acesso em: 15 set. 2011.

CUDA programming model. 2008. Disponível Em: . Acesso em: 20 set. 2012.

CUDA TOOLKIT 4.0 and Parallel Nsight 2.0. Traduzido por: Google Tradutor. Disponínel em: . Acesso em: 15 set. 2011.

75

DALLY, Bill. “PROJECT DENVER” PROCESSOR TO USHER IN NEW ERA OF COMPUTING. 2011. Disponível Em: . Acesso em: 2 out. 2012.

DONGARRA, Jack. O Que As Pessoas Estão Dizendo. Disponível em: . Acesso em: 29 mar. 2012.

EL-REWINI, H.; ABD-EL-BARR, M., Advanced Computer Architecture and Parallel Processing, 2005.

ENTENDA os supercomputadores, potentes máquinas usadas na pesquisa científica. 2011. Disponível Em: . Acesso em: 05 jun. 2012.

FERLIN, Edson P. O que é Processamento Paralelo?. 2011. Disponível Em: . Acesso em: 25 ago. 2012.

FLYNN, Michael J. Disponível em: . Acesso em: 25 out. 2011.

FROST, Gary. Aparapi: An Open Source tool for extending the Java promise of ‘Write Once Run Anywhere’ to include the GPU. Portland, 2012. Disponível Em: . Acesso em: 15 jun. 2012.

FUTURE generations of General-Purpose Graphics Processing Units (GPGPUs) look to improve performance per watt. 2011. Disponível Em: . Acesso em: 25 ago. 2012.

GPGPU. Disponível Em: . Acesso em: 02 ago. 2012.

GPGPU Computing Horizons: Developing and Deploying for Microsoft Windows. 2010. Disponível Em: . Acesso em: 25 out. 2012.

GPU DO PRINCÍPIO ATÉ A COMPUTAÇÃO DE PROPÓSITO GERAL. 2011. Disponível Em: . Acesso em: 21 out. 2012.

GRAMMELSBACHER, Alessandro V; MEDRADO, Jão C. C. COMPARAÇÃO DE DESEMPENHO ENTRE GPGPU E SISTEMAS PARALELOS. São Paulo, 2009. Disponível Em: . Acesso em: 11 jun. 2012.

76

HARRIS, Mark. CUDA 5 and Beyond. Disponível Em: . Acesso em: 18 set. 2012.

HOLZ, Lucas. Comparação de desempenho entre CPU e GPU usando CUDA. 2010. Trabalho de Conclusão de Curso apresentado como pré-requisito para conclusão do Curso de Ciência da Computação. Universidade Regional do Noroeste do Estado do Rio Grande do Sul. Ijuí, 2010.

HOUSTON, Mike. General Purpose Computation on Graphics Processors (GPGPU). Disponível Em: . Acesso em: 11 set. 2012.

HUANG, Jen-Hsun. NVIDIA CEO Talks Parallel and Shows Roadmap at GTC 2010. 2010. Disponível Em: . Acesso em: 02 out. 2012.

INTEL apresenta co-processador de 1 teraflops. 2011. Disponível Em: . Acesso em: 30 ago. 2012.

INÍCIO DA ERA DA APU AMD Fusion. Disponível em: . Acesso em: 26 mar. 2012.

JEONG, Min Kyu. CUDA ray-tracer. 2008. Disponível Em: . Acesso em: 11 jun. 2012.

KEPLER - THE WORLD'S FASTEST, MOST EFFICIENT HPC ARCHITECTURE. Disponível Em: . Acesso em: 01 ago. 2012.

LEPILOV Sergey; Zabelin Alexey. GPU Architecture and Positioning. 2012. Disponível Em: . Acesso em: 13 jun. 2012.

MAZZON, Tiago. Os Fractais. 2012. Disponível Em: . Acesso em: 28 out. 2012.

MONNERAT, Luiz R. R. Petrobras muda tecnologia e monta supercomputador. Portland, 2012. Disponível Em: . Acesso em: 18 ago. 2012.

MORAES, Sérgio Ricardo Dos Santos. COMPUTAÇÃO PARALELA EM CLUSTER DE GPU APLICADO A PROBLEMA DA ENGENHARIA NUCLEAR. Rio de Janeiro, 2012. Disponível Em:

77

. Acesso em: 14 set. 2012.

MORIMOTO, Carlos E. O ABC das placas 3D, parte 1. 2010. Disponível Em: . Acesso em: 13 set. 2012.

MORIMOTO, Carlos E. Processador. 2007. Disponível Em: . Acesso em: 28 set. 2012.

NON-GRAPHIC computing with graphics processors. 2008. Disponível Em: . Acesso em: 20 set. 2012.

NVIDIA GPUDIRECT. Disponível Em: . Acesso em: 17 ago. 2012.

NVIDIA Kepler GPUs Roadmap: GK107, GK106, GK104, GK110 and GK112. Disponível Em: . Acesso em: 21 jun. 2012.

NYLAND, Lars; HARRIS Mark; PRINS Jan. Fast N-Body Simulation with CUDA. Exemplos CUDA.

O QUE É A TECNOLOGIA de processamento gráfico CUDA?. Disponível em: . Acesso em: 14 set. 2011.

O QUE É COMPUTAÇÃO via GPU?. Traduzido por: Google Tradutor. Disponível em: . Acesso em: 15 set. 2011.

O que é computação com GPU?. Disponível Em: . Acesso em: 02 out. 2012.

O que são STREAM PROCESSORS?. 2007. Disponível Em: . Acesso em: 10 set. 2012.

Petrobras monta supercomputador mais rápido do Brasil. 2011. Disponível Em: . Acesso em: 25 dez. 2012

PFISTER, Greg. Supercomputadores de baixo custo estão com os dias contados. 2010. Disponível Em: . Acesso em: 10 jul. 2012.

78

QUIMICA COMPUTACIONAL. Disponível em: . Acesso em: 25 mar. 2012.

SANTOS, Fábio. GPU dos games à computação científica. 2011. Disponível Em: . Acesso em: 18 jul. 2012.

STEREOSCOPIC 3D Related News Coming from Computex 2010. 2010. Disponível Em: . Acesso em: 28 out. 2012.

SUPERCOMPUTADOR Chinês torna-se o mais rápido do mundo. Disponível em: . Acesso em: 27 mar. 2012.

SUPERCOMPUTADOR de mesa atinge 12 teraflops. 2009. Disponível Em: . Acesso em: 11 ago. 2012.

SUPERCOMPUTADOR Titan opera com mais de 18 mil GPUs NVIDIA. 2012. Disponível Em: . Acesso em: 03 nov. 2012.

TANENBAUM, Andrew S. Organização Estruturada de Computadores. 5.ed. São Paulo: Pearson Prentice Hall, 2007.

TAVARES, Arnaldo. Especialista da NVIDIA fala sobre processamento paralelo na USP. São Paulo, 2011. Disponível Em: . Acesso em: 25 ago. 2012.

TEIXEIRA, Sérgio. A era da computação verde. 2007. Disponível Em: . Acesso em: 20 mai. 2012.

THE SMX core. Disponível Em: . Acesso em: 20 out. 2012.

TOP500. Disponível em: . Acesso em: 15 set. 2011.

Tudo sobre Arquitetura ATI vs Nvidia (Stream Processor). 2009. Disponível Em: . Acesso em: 25 dez. 2012.

VOLPATO, Diego G. Exploração de Diferentes Níveis de Paralelismo Visando a Redução da Área de Processadores Embarcados. Porto Alegre, 2011. Disponível Em: . Acesso em: 26 mar. 2012.

79

YAMASHIRO, Fábio M; CORREA, Felipe. Paralelismo em Nível de Instrução. Disponível Em: . Acesso em: 14 jul. 2012.

80

OBRAS CONSULTADAS COMPUTAÇÃO de alto desempenho utilizando CUDA. Disponível em: . Acesso em: 15 set 2011.

CUDA – A Revolução Esperada. Disponível em: . Acesso em: 16 set 2011.

CUDA, Supercomputação para as Massas. Traduzido por: Google Tradutor. Disponível em: . Acesso em: 12 set 2011.

DALLALANA, Frederico José Camiloti. Uso das GPU para sistemas de Computação de Alto Desempenho. São José do Rio Preto. Disponível em: . Acesso em: 15 set 2011.

GPU – Graphics Processing Unit. Disponível em: . Acesso em: 15 set 2011.

GPU Computing in Nuclear Engineering. Disponível Em: . Acesso em: 8 jul 2012.

GPU: Aspectos Gerais. Disponível em: . Acesso em: 14 set 2011.

KIRK, David B; HWU, Wen-mei W. Programming Massively Parallel Processors. United States of America, 2010. Elsevier.

LAÍS, Aline. Green Computing Computacao Sustentavel. 2012. Disponível Em: . Acesso em: 16 jun 2012.

MAILLARD, N; NAVAUX, Philippe O. A. Novos rumos em programação para a exploração de paralelismo multi-nível. Disponível Em: . Acesso em: 27 jul 2012.

MORGAN, Timothy Prickett. NVIDIA's Kepler pushes parallelism up to eleven. 2012. Disponível Em: . Acesso em: 22 jul 2012.

ORGANIZAÇÃO e Arquitetura de Computadores. Disponível em: . Acesso em: 23 out 2011.

81

ANEXOS ANEXO A - Cálculo de Números Perfeitos, CUDA/C #include #include #include #include #include "temp.h"

__global__ void perfect(int *a, int N, int max, int threads) { int idx = blockIdx.x * blockDim.x + threadIdx.x;

int soma, b, trab, i; trab = max/threads;

for (i=0; i

if (soma==idx) { a[idx] = idx; } } idx +=threads; } } int main(int argc, char *argv) { int limit; printf ("\nNumeros PERFEITOS Usando CUDA\n Testar ate qual valor?\n"); scanf ("%d", &limit); int *a_h=0; // pointeiro da RAM int *a_d=0; // pointeiro da GPU

struct timeval tempo1, tempo2; long elapsed_mtime; gettimeofday(&tempo1, NULL);

int n_blocks = 32; int block_size = 1024;

const int N = n_blocks*block_size;// tamanho do vetor int i; int size = N * sizeof(int); // qtde de bytes do vetor

a_h = (int *)malloc(size);

for (i=0; i

cudaMalloc((void**)&a_d, size); cudaMemset(a_d, 0, size);

//copia da RAM para a GPU

82

cudaMemcpy(a_d, a_h, size, cudaMemcpyHostToDevice);

//printf("\n\nQt de blocos: %d",n_blocks);

//printf("\ntamanho do bloco: %d\n\n", block_size);

//executa o kernel na GPU perfect <<< n_blocks, block_size >>> (a_d, N, limit, n_blocks*block_size);

// copia da GPU para a RAM cudaMemcpy(a_h, a_d, size, cudaMemcpyDeviceToHost);

gettimeofday(&tempo2, NULL);

for (i=0; i

//libera memorias free(a_h); cudaFree(a_d);

elapsed_mtime = ((tempo2.tv_sec - tempo1.tv_sec) * 1000 + (tempo2.tv_usec - tempo1.tv_usec)/1000.0) + 0.5; printf("\nElapsed time = %ld milliseconds\n", elapsed_mtime); }

ANEXO A1 - Cálculo De Números Perfeitos, C #include #include "tempo.h" #include typedef struct { int id, inicio, fim; }thread_info; void *perfect(void *arg); void main(int argc, char *argv[]){ int limit, num_threads, trab;

printf ("\nNúmeros PERFEITOS!\nUsando pThread\n"); printf ("\nTestar até qual valor? "); scanf ("%d", &limit);

printf ("Executar com quantas Threads? "); scanf ("%d", &num_threads); trab = limit/num_threads; pthread_t thread_id[num_threads]; thread_info t_info[num_threads]; int i = 0; //Cria as threads tempo1(); for(i=0; i

83

if (i!=0) t_info[i].inicio = t_info[i-1].fim; else t_info[i].inicio = 2; if (i==num_threads-1) t_info[i].fim = limit; else t_info[i].fim = t_info[i].inicio+trab; pthread_create(&(thread_id[i]), NULL, perfect, (void *)&(t_info[i])); }

for(i=0; i

tempo2(); tempoFinal("mili segundos", "tempo", MSGLOG); } void *perfect(void *arg){ int soma, i, b; thread_info *thi = (thread_info *) arg; for (i=thi->inicio; i<=thi->fim; i++) { soma=0; if (i%2==0) { for (b=1; b<(i/2)+1; b++) { if (i%b==0) { soma = soma + b; } } if (soma==i) { printf("Thread %d - Nº Perfeito: %d\n", thi- >id, i); } } } }

ANEXO A2 - Cálculo de Números Primos, CUDA/C #include #include #include #include #include "temp.h"

__global__ void prime(int *a, int N, int max, int threads) { int idx = (blockIdx.x * blockDim.x) + threadIdx.x; int trab, i, c;

trab = max/threads;

for (i=0; i

84

{ a[idx] = idx; } idx +=threads; } } int main(int argc, char *argv) { int limit; printf ("\nNumeros PRIMOS Usando CUDA\n Testar ate qual valor?\n"); scanf ("%d", &limit); int *a_h=0; // pointeiro da RAM int *a_d=0; // pointeiro da GPU

struct timeval tempo1, tempo2; long elapsed_mtime; gettimeofday(&tempo1, NULL);

int n_blocks = 32; int block_size = 1024;

const int N = limit; // tamanho do vetor int i; int size = N * sizeof(int); // qtde de bytes do vetor

a_h = (int *)malloc(size);

for (i=0; i

cudaMalloc((void**)&a_d, size); cudaMemset(a_d, 0, size);

//copia da RAM para a GPU cudaMemcpy(a_d, a_h, size, cudaMemcpyHostToDevice);

//printf("\n\nQt de blocos: %d",n_blocks);

//printf("\ntamanho do bloco: %d\n\n", block_size);

//executa o kernel na GPU prime <<< n_blocks, block_size >>> (a_d, N, limit, n_blocks*block_size);

// copia da GPU para a RAM cudaMemcpy(a_h, a_d, size, cudaMemcpyDeviceToHost);

gettimeofday(&tempo2, NULL);

for (i=0; i 0) printf("\nN. Primo: %d", a_h[i]);

//libera memorias free(a_h); cudaFree(a_d);

elapsed_mtime = ((tempo2.tv_sec - tempo1.tv_sec) * 1000 + (tempo2.tv_usec - tempo1.tv_usec)/1000.0) + 0.5; printf("\nElapsed time = %ld milliseconds\n", elapsed_mtime);

85

}

ANEXO A3 - Cálculo de Números Primos, C #include #include "tempo.h" #include typedef struct { int id, inicio, fim; }thread_info; void *perfect(void *arg); void main(int argc, char *argv[]){ int limit, num_threads, trab;

printf ("\nNúmeros PRIMOS!\nUsando pThread\n"); printf ("\nTestar até qual valor? "); scanf ("%d", &limit);

printf ("Executar com quantas Threads? "); scanf ("%d", &num_threads); trab = limit/num_threads; pthread_t thread_id[num_threads]; thread_info t_info[num_threads]; int i = 0; //Cria as threads tempo1(); for(i=0; i

for(i=0; i

tempo2(); tempoFinal("mili segundos", "tempo", MSGLOG); } void *perfect(void *arg){ int soma, i, b, c; thread_info *thi = (thread_info *) arg; for (i=thi->inicio; i<=thi->fim; i++) { for ( c = 2 ; c <= i - 1 ; c++ ) {

86

if ( i%c == 0 ) break; } if ( c == i ) { printf("Thread %d - Nº Primo: %d\n", thi->id, i); } } }

ANEXO A4 - Parte do Código de Multiplicação De Matrizes, CUDA/C Fonte: Exemplos CUDA // CUBLAS version 2.0 { cublasHandle_t handle; cublasStatus_t ret; ret = cublasCreate(&handle); if (ret != CUBLAS_STATUS_SUCCESS) { printf("cublasCreate returned error code %d, line(%d)\n", ret, __LINE__); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); }

const float alpha = 1.0f; const float beta = 0.0f; //Perform warmup operation with cublas ret = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, matrix_size.uiWB, matrix_size.uiHA, matrix_size.uiWA, &alpha, d_B, matrix_size.uiWB, d_A, matrix_size.uiWA, &beta, d_C, matrix_size.uiWA);

if (ret != CUBLAS_STATUS_SUCCESS) { printf("cublasSgemm returned error code %d, line(%d)\n", ret, __LINE__); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); }

// Allocate CUDA events that we'll use for timing cudaEvent_t start; error = cudaEventCreate(&start);

if (error != cudaSuccess) { fprintf(stderr, "Failed to create start event (error code %s)!\n", cudaGetErrorString(error)); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); }

cudaEvent_t stop; error = cudaEventCreate(&stop);

if (error != cudaSuccess) { fprintf(stderr, "Failed to create stop event (error code %s)!\n", cudaGetErrorString(error)); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); }

// Record the start event error = cudaEventRecord(start, NULL);

if (error != cudaSuccess) { fprintf(stderr, "Failed to record start event (error code

87

%s)!\n", cudaGetErrorString(error)); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); }

for (int j = 0; j < nIter; j++) { //note cublas is column primary! //need to transpose the order ret = cublasSgemm(handle, CUBLAS_OP_N, CUBLAS_OP_N, matrix_size.uiWB, matrix_size.uiHA, matrix_size.uiWA, &alpha, d_B, matrix_size.uiWB, d_A, matrix_size.uiWA, &beta, d_C, matrix_size.uiWA);

if (ret != CUBLAS_STATUS_SUCCESS) { printf("cublasSgemm returned error code %d, line(%d)\n", ret, __LINE__); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); } }

printf("done.\n");

// Record the stop event error = cudaEventRecord(stop, NULL);

if (error != cudaSuccess) { fprintf(stderr, "Failed to record stop event (error code %s)!\n", cudaGetErrorString(error)); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); }

// Wait for the stop event to complete error = cudaEventSynchronize(stop);

if (error != cudaSuccess) { fprintf(stderr, "Failed to synchronize on the stop event (error code %s)!\n", cudaGetErrorString(error)); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); }

float msecTotal = 0.0f; error = cudaEventElapsedTime(&msecTotal, start, stop);

if (error != cudaSuccess) { fprintf(stderr, "Failed to get time elapsed between events (error code %s)!\n", cudaGetErrorString(error)); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); } printf("\nGPU ELAPSED: %.3f\n", double(clock() - gpuBegin) / CLOCKS_PER_SEC); // Compute and print the performance float msecPerMatrixMul = msecTotal / nIter; double flopsPerMatrixMul = 2.0 * (double)matrix_size.uiWA * (double)matrix_size.uiHA * (double)matrix_size.uiWB; double gigaFlops = (flopsPerMatrixMul * 1.0e-9f) / (msecPerMatrixMul / 1000.0f); printf( "Performance= %.2f GFlop/s, Time= %.3f msec, Size= %.0f Ops\n", gigaFlops, msecPerMatrixMul, flopsPerMatrixMul);

88

// copy result from device to host error = cudaMemcpy(h_CUBLAS, d_C, mem_size_C, cudaMemcpyDeviceToHost);

if (error != cudaSuccess) { printf("cudaMemcpy h_CUBLAS d_C returned error code %d, line(%d)\n", error, __LINE__); shrQAFinishExit(argc, (const char **)argv, QA_FAILED); } }

ANEXO B - Cálculo de Números Perfeitos, JAVA package test; import com.amd.aparapi.Kernel; public class Perfeitos extends Thread {

private int range; private int perfeitos[]; public static final int CPU = 0; public static final int GPU = 1;

public Perfeitos(int mode, int range) { this.range = range; perfeitos = new int[range]; if (mode == CPU) { onCPU(); } else { onGPU(); } }

public static void main(String[] args) { new Perfeitos(GPU, 1000000); }

private void onCPU() { int threads = 20; for (int i = 1; i <= threads; i++) { int trab = Math.round(range / threads); int fim = trab * i; int ini = (fim - trab) + 1; new Perfeitos.CpuThread(ini, fim).start(); } // for (int i = 0; i < perfeitos.length; i++) { // if (perfeitos[i] != 0) { // System.out.println(perfeitos[i]); // } // } }

private void onGPU() { Perfeitos.GpuThread gt = new Perfeitos.GpuThread(range); gt.setExecutionMode(Kernel.EXECUTION_MODE.GPU); int perfeitos[] = gt.getPerfeitos(); System.out.println(gt.getExecutionTime() + "ms"); // for (int i = 0; i < perfeitos.length; i++) { // if (perfeitos[i] != 0) { // System.out.println(perfeitos[i]);

89

// } // } }

//CPU THREAD WORK public class CpuThread extends Thread {

private int ini; private int fim; // private int perfeitos[];

public CpuThread(int ini, int fim) { this.ini = ini; this.fim = fim; setPriority(MAX_PRIORITY); }

@Override public void run() { for (int ate = ini; ate <= fim; ate++) { int soma = 0; for (int i = 1; i < ate; i++) { if (ate % i == 0) { soma += i; } } if (ate == soma) { perfeitos[ate] = ate; } } } }

//GPU THREAD WORK public class GpuThread extends Kernel {

final int range; final int perfeitos[];

public GpuThread(final int range) { this.range = range; this.perfeitos = new int[range]; }

@Override public void run() { int soma = 0; for (int i = 1; i < getGlobalId(); i++) { if ((getGlobalId() % i) == 0) { soma += i; } } if (soma == getGlobalId()) { perfeitos[getGlobalId()] = getGlobalId(); } }

public int[] getPerfeitos() { execute(range); return perfeitos; }

90

} }

ANEXO B1 - Cálculo de Números Primos, JAVA package test; import com.amd.aparapi.Kernel; public class Primos extends Thread {

private int range; private int primos[]; public static final int CPU = 0; public static final int GPU = 1;

public Primos(int mode, int range) { this.range = range; primos = new int[range]; if (mode == CPU) { onCPU(); } else { onGPU(); } }

public static void main(String[] args) { new Primos(GPU, 10000); }

private void onCPU() { int threads = 20; for (int i = 1; i <= threads; i++) { int trab = Math.round(range / threads); int fim = trab * i; int ini = (fim - trab) + 1; new CpuThread(ini, fim).start(); } // for (int i = 0; i < primos.length; i++) { // if (primos[i] != 0) { // System.out.println(primos[i]); // } // } }

private void onGPU() { GpuThread gt = new GpuThread(range); gt.setExecutionMode(Kernel.EXECUTION_MODE.GPU); int primos[] = gt.getPrimos(); System.out.println(gt.getExecutionTime() + "ms"); // for (int i = 0; i < primos.length; i++) { // if (primos[i] != 0) { // System.out.println(primos[i]); // } // } }

//CPU THREAD WORK public class CpuThread extends Thread {

private int ini;

91

private int fim; // private int primos[];

public CpuThread(int ini, int fim) { this.ini = ini; this.fim = fim; setPriority(MAX_PRIORITY); }

@Override public void run() { for (int ate = ini; ate <= fim; ate++) { int primo = 0; for (int i = 2; i < ate; i++) { if ((ate % i) == 0) { primo++; } } if (primo == 0) { primos[ate] = ate; } } } }

//GPU THREAD WORK public class GpuThread extends Kernel {

final int range; final int primos[];

public GpuThread(final int range) { this.range = range; this.primos = new int[range]; }

@Override public void run() { int primo = 0; for (int i = 2; i < getGlobalId() + 1; i++) { if (((getGlobalId() + 1) % i) == 0) { primo++; } } if (primo == 0) { primos[getGlobalId() + 1] = getGlobalId() + 1; } }

public int[] getPrimos() { execute(range); return primos; } } }

ANEXO B2 - Multiplicação de Matrizes, JAVA package test; import com.amd.aparapi.Kernel;

92 import java.util.Random; public class Matrix {

public static void main(String[] args) throws Exception { final int r = 3200; final int c1 = r; final int c2 = r; AparapiMatMul ap = new AparapiMatMul(r, c1, c2);

try { ap.setExecutionMode(Kernel.EXECUTION_MODE.GPU); // ap.setExecutionMode(Kernel.EXECUTION_MODE.CPU); ap.execute(r, c2); System.out.println(ap.getExecutionTime() + "ms"); } catch (NullPointerException ne) { ne.printStackTrace(); } ap.dispose(); } } class AparapiMatMul extends Kernel {

double matA[]; double matB[]; double matC[]; double C[]; int rows; int cols1; int cols2;

@Override public void run() { int i = getGlobalId(); int j = getPassId(); double value = 0; for (int k = 0; k < cols1; k++) { value += matA[k + i * cols1] * matB[k * cols2 + j]; } matC[i * cols1 + j] = value; }

public AparapiMatMul(int r, int c1, int c2) { rows = r; cols1 = c1; cols2 = c2;

matA = new double[r * c1]; matB = new double[c1 * c2]; matC = new double[r * c2]; C = new double[r * c2]; //matC should be initialized with zeros for (int i = 0; i < r; i++) { for (int j = 0; j < c1; j++) { matC[i * c1 + j] = 0;

} }

for (int i = 0; i < r; i++) { for (int j = 0; j < c1; j++) {

93

matA[i * c1 + j] = new Random().nextDouble(); } }

for (int i = 0; i < r; i++) { for (int j = 0; j < c1; j++) { matB[i * c2 + j] = new Random().nextDouble(); } } } }