Você está na página 1de 48

Captulo

7
Introduo Computao Heterognea
Denise Stringhini, Rogrio A. Gonalves, Alfredo Goldman

Resumo Diversos tipos de coprocessadores tem sido utilizados a fim de acelerar a execuo de aplicaes que executam em um n computacional. Estes coprocessadores tem sido chamados de aceleradores e so responsveis pela natureza heterognea destes ns computacionais. Este tutorial introduz conceitos bsicos de computao heterognea a partir de uma das opes mais populares entre os aceleradores, que so as GPUs. Num primeiro momento apresentada uma contextualizao, onde so descritos os principais componentes de sua arquitetura, relacionando-os com os modelos SIMD, SPMD e SIMT, largamente explorados em computao heterognea. Em seguida, o tutorial apresenta as alternativas de ambientes e ferramentas de programao para estes aceleradores, juntamente com exemplos de programao heterognea que unem CPU e GPU. Neste contexto, so apresentados ambientes de programao tais como CUDA, OpenCL, OpenMP e OpenACC. Eles podem ser usados para se obter paralelismo em arquiteturas heterogneas compostas de CPU e GPU. Abstract Several types of coprocessors have been used in order to accelerate the execution of one node only applications. These coprocessors have been named accelerators and are responsible for the heterogeneous nature of these computational nodes. This tutorial introduces heterogeneous computing basic concepts from one the most popular accelerators options that are the GPUs. First, a contextualization is presented where its main architecture components are described and related to the SIMD, SPMD and SIMT models, widely explored in heterogeneous computing. After that, the tutorial presents the alternatives in programming environments and tools for these accelerators along with heterogeneous programming examples that unify CPU and GPU. In this context, programming environments as CUDA, OpenCL, OpenMP and OpenACC are presented. They could be used in order to obtain parallelism in heterogeneous architectures composed by CPU and GPU.

7.1.

Introduo

O termo computao heterognea tem sido empregado para designar o uso de diferentes arquiteturas em um mesmo n computacional a fim de se obter melhores desempenhos das aplicaes que executaro neste n. Entre as alternativas destacam-se arquiteturas como GPUs (Graphics Processing Units) e FPGAs (Field-programmable Gate Arrays), ambos tambm conhecidos como aceleradores. Devido aos ganhos de desempenho recentemente observados em aplicaes que utilizam tais aceleradores, assim como seu baixo consumo de energia, fabricantes como Intel, AMD e ARM tem anunciado projetos de chips heterogneos. Neste contexto, necessrio um bom conhecimento dos modelos de programao paralela empregados nessas arquiteturas. Em geral, os modelos de programao dos aceleradores correspondem a uma evoluo do modelo SIMD, onde uma mesma instruo aplicada sobre um conjunto de dados em paralelo. Em GPUs, por exemplo, um mesmo trecho de cdigo, chamado kernel, replicado em at milhares de threads que executam de forma concorrente sobre um grande conjunto de dados. O principal objetivo deste tutorial justamente apresentar diferentes ambientes e ferramentas de programao que permitem que um cdigo sendo executado na CPU possa enviar trabalho para um acelerador. A heterogeneidade deste tipo de computao est justamente em se desenvolver aplicaes que explorem todos os dispositivos presentes na mquina. Alm disso, importante salientar que o modelo de programao dos aceleradores no adequado a todo o tipo de algoritmo. Assim, importante que o desenvolvedor saiba identificar as tarefas mais adequadas para a execuo num acelerador e aquelas mais adequadas execuo numa CPU tradicional. O estudo das arquiteturas dos aceleradores, assim como dos ambientes de desenvolvimento disponveis pode auxiliar os desenvolvedores a ter esta percepo. Este tutorial apresenta os principais ambientes e ferramentas disponveis para a computao heterognea com aceleradores do tipo GPU, juntamente com exemplos introdutrios. A segunda seo apresenta brevemente a nomenclatura relacionada ao paralelismo que utilizada no restante do texto. A terceira seo tem a funo de contextualizar as GPUs, foco deste tutorial, entre as principais arquiteturas heterogneas em utilizao no momento. A quarta seo apresenta as arquiteturas das GPUs mais atuais da NVIDIA, a Fermi e a Kepler, que sero os modelos de aceleradores utilizados como principal exemplo neste tutorial. A quinta seo apresenta o OpenMP que permite utilizar threads na CPU e pode ser utilizado em conjunto com os ambientes de programao para aceleradores. A sexta seo aborda a plataforma de programao CUDA para GPUs, enquanto a stima apresenta a alternativa OpenCL, com uma abordagem mais ampla (heterognea) que pretende abranger uma srie de dispositivos, principalmente CPUs e GPUs. A oitava seo apresenta o OpenACC que possui um conjunto de primitivas semelhantes ao OpenMP, porm com utilizao em GPUs. Dado que a grande maioria destas ferramentas e ambientes so apresentados para a linguagem C, a ltima seo apresenta algumas alternativas para que se possa utilizar aceleradores do tipo GPU em outras linguagens (Java, Python e C++/STL).

7.2.

Conceitos de paralelismo

O objetivo desta seo apresentar os principais conceitos de paralelismo relacionados computao heterognea. Neste sentido, a terminologia utilizada em HPC (High Performance Computing) ou PAD no portugus (Processamento de Alto Desempenho) apresentada a fim de familiarizar os leitores/audincia com os termos a serem usados ao longo do captulo. 7.2.1. Processamento de Alto Desempenho A busca por alto desempenho de aplicaes fez com que as melhorias se expandissem para domnios externos aos de arquiteturas convencionais. Os clusters de computadores combinados e interconectados com uma rede de alta velocidade, multiprocessadores de memria compartilhada (SMP) conectando mltiplos processadores em um nico sistema de memria e os Chip Multi-Processor (CMP) contendo mltiplos ncleos integrados no mesmo chip, so exemplos de mquinas paralelas. notvel que as mquinas mais rpidas do mundo hoje em dia, relacionadas no site TOP500 (www.top500.org), possuem paralelismo em todos os nveis de suas arquiteturas. Embora praticamente todas as mquinas relacionadas sejam clusters ou MPPs (Massively Parallel Processors), compostos de centenas de ns de processamento, estes ns so cada vez mais heterogneos, onde o paralelismo pode ser explorado de diferentes maneiras. Segundo a lista TOP500 de novembro de 2011, o computador mais rpido do mundo, o computador K do Japo, conseguiu um desempenho de 10.51 PetaFlop/s usando o benchmark Linpack. Ao contrrio de vrios dos sistemas recentes na lista, o K no usa placas de processamento grfico nem outros tipo de aceleradores. Por outro lado, os supercomputadores nas posies 2, 4 e 5 usam GPUs da NVIDIA nos clculos. Alm disso, 39 supercomputadores da lista tambm usam GPUs como aceleradores, este nmero era de apenas 17 na edio anterior. Destes 39 supercomputadores, 35 usam placas da NVIDIA, 2 usam processadores Cell e dois usam placas ATI Radeon. Observando que estes 39 supercomputadores so apenas 7,8% do total de 500, um outro dado relevante que o poder de clculo destes computadores corresponde a mais de 15% do poder total de toda a lista. Recentemente, portanto, as atenes tem-se voltado ao estudo e explorao do processamento paralelo fornecido por elementos de processamento grfico (GPUs) para o processamento de aplicaes de propsito geral, ou seja, aplicaes cientficas e no somente grficas sendo executadas pela integrao CPU-GPU. Esta integrao, por sua vez, realizada atravs da diviso do cdigo a ser executado na CPU e na GPU. Para fins de acelerao de execuo, pores menores do cdigo, sejam elas sequenciais ou levemente paralelas podem ser escalonadas para a CPU atravs de modelos de programao como o OpenMP (CHAPMAN, 2007) e o cdigo fortemente baseado em paralelismo de dados e laos que dominam o tempo de execuo de uma aplicao, para uma GPU. De maneira mais generalizada estamos tratando do que chamada de Computao Heterognea, que considera elementos de processamento, sejam CPUs multicore ou manycore, sejam GPUs manycore e por que no elementos de processamento reconfigurveis (FPGAs), que estejam disponveis na mquina

hospedeira (host) ou em um agrupamento de hosts (cluster, grid). Esses elementos especializados podem ser usados em conjunto com processadores tradicionais, funcionando como aceleradores para a execuo de tarefas especficas. A generalizao de domnio uma questo crtica, pois solues ainda so bem especficas considerando-se o domnio da tcnica nos elementos mais simples da hierarquia. A ideia de integrar esses elementos de processamento para a generalizao de domnio possibilita solues de problemas de complexidade maior, porm a tentativa de generalizao do domnio no uma tarefa trivial. Mquinas paralelas exploram o paralelismo em diferentes nveis, realizando computao no nvel de instrues, de dados ou de tarefas, o que leva a uma classificao conforme o nvel no qual o hardware d suporte ao paralelismo. 7.2.2. Classificaes e terminologia Esta seo abrange os principais termos e classificaes usados para descrever tanto mquinas paralelas quanto os modelos de programao correspondentes. importante para que o leitor se familiarize com os termos encontrados em qualquer texto da rea. Alguns deles so apresentados a seguir. possvel afirmar que a Taxonomia de Flynn (Flynn, 1972) uma das mais clssicas da rea de PAD, embora seja genrica e no abranja de forma detalhada todos os tipos de arquiteturas existentes hoje em dia. Entretanto, alguns de seus termos so usados at hoje para descrever o tipo de paralelismo encontrado em algumas arquiteturas: SISD (Single Instruction, Single Data): No expressam nenhum paralelismo, classe que representa as arquiteturas que trabalham com um nico fluxo de instrues e um nico fluxo de dado, remete ao Modelo de von Neumann com execuo sequencial; SIMD (Single Instruction, Multiple Data): Mquinas paralelas que executam exatamente o mesmo fluxo de instrues (mesmo programa) em cada uma das suas unidades de execuo paralela, considerando fluxos de dados distintos; MIMD (Multiple Instruction, Multiple Data): Mquinas paralelas que executam fluxos independentes e separados de instrues em suas unidades de processamento. Pode-se ter programas diferentes, independentes que processam entradas diferentes, mltiplos fluxos de dados. Outra classificao essencial aquela que divide as mquinas segundo o compartilhamento de memria. Esta classificao importante, pois diretamente responsvel pelo modelo de programao a ser empregado no desenvolvimento das aplicaes: Multiprocessadores: mquinas com memria compartilhada (UMA e NUMA, respectivamente com velocidades de acesso uniforme memria e no uniforme), possibilitam o modelo de programao multithreaded. Multicomputadores: mquinas onde cada elemento de processamento possui a sua prpria memria (NORMA), exigem o modelo de programao atravs da troca de mensagens entre seus componentes.

Ainda existem outras classificaes usuais considerando-se o nvel de abstrao: SPMD (Single Program, Multiple Data): O modelo SPMD (Single Program, Multiple Data) foi definido por (Darema-Rogers and Pfister, 1985) (DAREMA, 2001), este modelo mais geral que o modelo SIMD (Single Instruction, Multiple Data) e que o modelo data-parallel, pois o SPMD usa aplicaes com paralelismo de dados e paralelismo de threads; SIMT (Single Instruction, Multiple Threads): Modelo utilizado para gerenciar a execuo de vrias threads na arquitetura. Termo utilizado pela Nvidia para o CUDA; MSIMD (Multiple SIMD): Mquinas que possuem um memria global de tamanho ilimitado, e P processadores SIMD independentes. Autores definiram (Bridges, 1990) e utilizam (Jurkiewicz and Danilewski, 2011) esta denominao.

7.3.

Arquiteturas heterogneas

Esta seo tem o objetivo de definir e apresentar as principais arquiteturas heterogneas e contextualizar aquela que ser foco no restante do captulo, ou seja, a das GPUs. Dongarra, 2009, prope uma taxonomia para plataformas heterogneas que pode ser utilizada para contextualizar as arquiteturas heterogneas abordadas neste captulo. Esta taxonomia inclui mquinas paralelas e sistemas distribudos e possui as seguintes categorias em ordem crescente de heterogeneidade e complexidade: sistemas heterogneos projetados por fabricantes especficos; clusters heterogneos; redes locais de computadores; redes globais de computadores em um mesmo nvel organizacional; redes globais de computadores de propsito geral. Como possvel observar, existem vrias classes de sistemas heterogneos, onde a maioria recai em sistemas distribudos (trs ltimas categorias). Este captulo, entretanto, aborda apenas a primeira categoria, a de sistemas heterogneos projetados por fabricantes especficos. O interesse, neste caso, so os dispositivos denominados aceleradores que atuam em conjunto com as CPUs dentro de um nico n de processamento. Atualmente, observa-se a tendncia em se utilizar uma quantidade maior de ncleos de processamento para se obter desempenho, ao invs de aumentar a frequncia do clock. Em (Borkar, 2011) so apresentados o histrico dos ltimos trinta anos e algumas perspectivas para as arquiteturas de microprocessadores para os prximos vinte anos. Num primeiro momento, os autores traam um perfil dos ltimos anos onde o desempenho era obtido a partir de um aumento na velocidade de clock, juntamente com a otimizao das instrues e o aprimoramento da hierarquia de cache. Do ponto de vista das aplicaes, os compiladores mantinham a tarefa de adaptar o cdigo para executar num hardware mais sofisticado e obter melhores desempenhos.

Nos prximos vinte anos, porm, o principal fator que guiar os projetos de microprocessadores j e continuar sendo o gerenciamento de energia. O problema que, de uma maneira geral, quanto mais sofisticados os ncleos, maior o consumo de energia. Alm disso, chegamos num momento em que esta sofisticao e o consequente maior gasto de energia j no traz mais tanto desempenho. Assim, a utilizao de ncleos mais simples, que executam no modelo SIMD, juntamente com uma certa quantidade de ncleos mais sofisticados a tendncia natural para os prximos vinte anos. Esta abordagem tem sido caracterizada como computao heterognea e o foco deste captulo. No momento, os aceleradores so uma alternativa de baixo custo que j pode ser utilizada em conjunto com as CPUs de mercado para realizar computao heterognea. Possuem a vantagem de melhorar o desempenho de uma srie de aplicaes altamente iterativas e que possuem um tipo paralelismo denominado trivial, onde a dependncia de dados no existe ou mnima. Assim surge o modelo de computao heterognea onde as tarefas so atribudas ao hardware mais adequado presente na mquina. A seguir, so apresentados trs exemplos de aceleradores. A arquitetura de GPU ter maior destaque na prxima seo. 7.3.1. Exemplos de aceleradores Esta seo apresenta trs exemplos de aceleradores, buscando salientar semelhanas e diferenas em suas arquiteturas, assim como os modelos de programao: Cell BEA, FPGAs e GPUs. As prximas sees apresentam com maior detalhe as GPUs da NVIDIA e o seu modelo de programao. 7.3.1.1 Cell BEA O Cell BEA (Cell Broadband Engine Architecture) (Crawford, 2008) j apareceu como componente acelerador em mquinas no topo do TOP 500 (www.top500.org), o ranking que lista as mquinas com maior poder de processamento do mundo. Trata-se de uma arquitetura notria por equipar tanto o console do Playstation 3 quanto o Roadrunner, a primeira mquina a ultrapassar a marca dos Petaflops na lista do TOP 500. Sua configurao era composta por quatro CBE PowerXCell8i de 3.2 Ghz e dois dual-core Opteron de 1.8 Ghz em cada n de processamento (Crawford, 2008). O Cell BE um processador heterogneo equipado com uma CPU tradicional (PPE - Power Processing Element) alm de oito cores mais simples de propsito especfico (SPE - Synergistic Processing Elements) em um mesmo chip. A Figura 7.1 apresenta um esquema da arquitetura do Cell BE. Os principais elementos presentes so o PPE, os oito SPEs e os controladores de barramento e de memria. O barramento (EIB Element Interconect Bus), em especial, estruturado em dois anis para cada direo que conectam todos os elementos. O PPE um ncleo de processamento tpico, similar aos encontrados em mquinas baseadas em processadores Power. Em particular, as instrues de leitura e escrita na memria so realizadas atravs de uma tpica hierarquia de cache. Os SPEs, entretanto, so ncleos menos complexos e menores, projetados para serem ncleos aceleradores que concentram a maior parte do poder de processamento do Cell BE (Kunzman, 2009). Ambos os tipos de ncleos possuem instrues do tipo SIMD em seu

conjunto de instrues, o que ajuda a acelerar as aplicaes que possuam caractersticas vetoriais.

Figura 7.1: componentes do Cell BEA (Fonte: Dongarra, 2009)

A programao dos SPEs, entretanto, considerada um tanto complexa. Em primeiro lugar, os SPEs no possuem hierarquia de cache. Ao invs disso, possuem uma memria local (local storage) que deve ser gerenciada explicitamente no cdigo da aplicao. Em segundo lugar, as transferncias de memria devem ser realizadas via DMA (Direct Memory Access) que tambm devem ser iniciadas de forma explcita no cdigo da aplicao. Por ltimo, os SPEs tem seu prprio conjunto de instrues (ISA Instruction Set Architecture), o que faz com que seu cdigo tenha que ser compilado separadamente do cdigo principal da aplicao que executa no PPE e responsvel por iniciar as execues nos SPEs (Kunzman, 2009). Embora seja um bom exemplo de arquitetura heterognea utilizada para PAD, o processador que equipou o Roadrunner (o PowerXCell8i) j no ser utilizado pela IBM para construir supercomputadores (Wolfe, 2012), fato que torna esta arquitetura uma escolha um tanto duvidosa para uso em PAD. 7.3.1.2 FPGA FPGAs (Field Programmable Gate Array Architecture) tem sido empregados recentemente como aceleradores para PAD devido aos avanos obtidos principalmente nas tecnologias de interconexes de alta velocidade (Brodtkorb, 2010). Embora sua arquitetura seja mais simplificada do que a dos demais aceleradores, permite um alto grau de paralelismo de dados em instrues como adies e multiplicaes. No contexto de PAD, alguns dos maiores fabricantes de FPGAs, tais como a Xilink e a Altera, tem colaborado com fabricantes como a Convey e a Cray que oferecem placas compatveis com processadores Intel ou AMD usando o mesmo barramento de alta velocidade e memria na placa. Um FPGA consiste num conjunto de blocos lgicos reconfigurveis, alm de blocos de processamento digital de sinais e opcionalmente um ou mais ncleos de CPU tradicionais todos conectados por uma interconexo extremamente flexvel. Esta interconexo reconfigurvel e pode ser utilizada para adaptar as aplicaes s FPGAs.

Quando configuradas, as FPGAs funcionam com os circuitos integrados feitos para aplicaes especficas (ASICs application specific integrated circuits) (Brodtkorb, 2010). A Figura 7.2 apresenta um esquema genrico para um sistema heterogneo composto de uma CPU e de um FPGA. Existem verses compatveis com CPUs Intel e AMD.

Figura 7.2: sistema heterogneo com CPU e FPGA (Fonte: Brodtkorb, 2010)

Do ponto de vista do uso de FPGAs para PAD, nem todas as aplicaes podem facilmente obter speedup. Embora (Storaasli, 2007) descreva ganhos de at 100x obtidos em uma aplicao de biologia computacional (comparao de sequncias de DNA), o modelo de programao ainda considerado complexo. As principais ferramentas de programao so as linguagens VHDL e Verilog, que exigem um conhecimento especializado. Alm destas, (Brodtkorb, 2010) ainda cita, entre outras, Mitrion-C e System-C, que permitem programao em linguagem C com extenses para acesso FPGA. Outra opo a ferramenta Viva, que possui um ambiente grfico de programao similar ao do Labview. 7.3.1.3 GPU O exemplo mais contundente de acelerador no momento so as GPUs (Graphics Processing Units). Inicialmente possuam apenas funo grfica, mas rapidamente seu potencial foi percebido e verses para processamento genrico (GPGPU General Purpose GPU) surgiram. Hoje em dia possvel observar algumas mquinas equipadas com centenas de GPUs no topo da lista do TOP 500. As GPUs so dispositivos aceleradores que atuam em conjunto com as CPUs. Os dois principais fabricantes de GPUs no momento so a NVIDIA e a AMD que podem atuar em conjunto com CPUs Intel ou AMD. O paralelismo do tipo SIMD, onde milhares de threads executam simultaneamente nas centenas de ncleos presentes na GPU. O programa principal executa na CPU (host) e o responsvel por iniciar as threads na GPU (device). As GPUs tem sua prpria hierarquia de memria e os dados devem ser transferidos atravs de um barramento PCI express. A Figura 7.3 mostra um esquema genrico de um sistema heterogneo composto de uma CPU e de uma GPU. A NVIDIA a principal fabricante de GPUs para PAD e equipa algumas mquinas presentes entre as dez mais rpidas do mundo, por exemplo. Alm de serem aceleradores eficientes, as GPUs tem um baixo consumo de energia comparados a multicores, por isso tambm so opes adotadas em computao verde (green computing) (www.green500.org).

Figura 7.3: GPU em combinao com uma CPU (Fonte: Brodtkorb, 2010)

Os principais ambientes de programao para GPUs so CUDA (NVIDIA) e OpenCL (padro aberto para computao heterognea). Enquanto CUDA pode ser utilizada apenas em placas da NVIDIA, OpenCL um padro que pode ser utilizado com uma srie de dispositivos, o que inclui as placas da NVIDIA e da AMD. OpenCL um ambiente um pouco mais recente e somente as ltimas verses comeam a ter desempenho suficiente para alcanar os obtidos por CUDA. Um dos principais problemas de OpenCL manter o desempenho enquanto preserva a portabilidade entre dispositivos diferentes (Du, 2010). As prximas sees iro detalhar a arquitetura e as opes de ferramentas de desenvolvimento para GPUs, por serem as opes mais populares em termos de aceleradores hoje em dia. Em termos de arquitetura, a nfase ser dada arquitetura Fermi da NVIDIA, enquanto que os ambientes de programao abordados para GPU sero CUDA, OpenCL e OpenACC.

7.4.

Arquitetura das GPUs

Esta seo apresenta com maior detalhe a arquitetura das GPUs, em especial a Fermi da NVIDIA, que ser usada como exemplo principal. Esta arquitetura permite a execuo concorrente de kernels, entre outras novidades com relao s suas antecessoras. A Figura 7.4 demonstra o crescimento comparativo da capacidade de realizao de operaes de ponto flutuante por segundo (FLOPS/s) entre GPU e CPU. A srie GTX 400 introduziu o uso da arquitetura Fermi, enquanto que a srie GTX 600 introduziu a arquitetura Kepler, que sero vistas neste captulo. As GPUs so compostas de centenas de ncleos (cores) simples que executam o mesmo cdigo atravs de centenas a milhares de threads concorrentes. Esta abordagem se ope ao modelo tradicional de processadores multicore, onde algumas unidades de ncleos completos e independentes so capazes de processar threads ou processos. Estes ncleos completos, as CPUs, possuem poderosas unidades de controle e de execuo capazes de executar instrues paralelas e fora de ordem, alm de contarem com uma poderosa hierarquia de cache. J as GPUs contam com unidades de controle e de execuo mais simples, onde uma unidade de despacho envia apenas uma instruo para um conjunto de ncleos que a executaro em ordem. O modelo de execuo das GPUs conhecido como SIMT (Single Instruction Multiple Threads), derivado do clssico termo SIMD (Single Instruction Multiple Data).

Figura 7.4: evoluo de desempenho das GPUs (fonte: NVIDIA, 2012a)

Este modelo exige uma nova postura por parte dos desenvolvedores de software, portando importante que esta arquitetura seja desvendada antes de apresentar o modelo de programao. Outra caracterstica importante a hierarquia de memria. As GPUs, possuem memria global que pode ser acessada por todas as threads, porm as mais modernas j contam com caches de nvel 1 e de nvel 2. Alm disso, outras memrias especializadas tambm podem ser usadas para acelerar o processamento. 7.4.1. Arquitetura Fermi A arquitetura Fermi da NVIDIA segue este princpio de dedicar uma maior quantidade de transstores s unidades de execuo, ao invs de dedica-los s unidades de controle e cache. A Figura 7.5 apresenta uma viso geral da arquitetura Fermi.

Figura 7.5: viso geral da arquitetura Fermi (fonte: NVIDIA, 2009)

Esta arquitetura (Figura 7.5) conta com 16 SM (Streaming Multiprocessors), cada

um composto por 32 ncleos de processamento (cores), resultando num total de 512 ncleos. possvel observar uma cache de segundo nvel (L2) compartilhada por todos os SM. A cache de primeiro nvel (L1) compartilhada pelos 32 ncleos de cada SM. A memria compartilhada (shared memory) pode ser usada explicitamente pelo programador como uma memria de rascunho que pode acelerar o processamento de uma aplicao, dependendo do seu padro de acesso aos dados. Esta memria dividida fisicamente com a cache de primeiro nvel com um total de 64KB, cujo tamanho configurvel: 16 KB 48KB para cache e memria compartilhada respectivamente ou ao contrrio. Alm dos dois nveis de cache e da memria compartilhada, a Fermi conta com uma memria global (DRAM) de at 6GB. A Figura 7.6 apresenta os principais componentes de um SM que compe a arquitetura Fermi, apresentando seus ncleos simplificados, as unidades de escalonamento e despacho de instrues, os registradores, a cache de nvel 1, entre outros.

Figura 7.6: arquitetura de um SM (fonte: NVIDIA, 2009)

Cada SM composto por quatro blocos de execuo controlados por duas unidades escalonamento de warps (grupos de 32 threads): dois blocos de 16 ncleos simples, capazes de executar em ordem uma instruo inteira ou de ponto flutuante, um bloco de 16 unidades load/store e um bloco de 4 unidades de processamento de instrues

especiais (SFU Special Function Units). Alm disso, cada SM conta com uma memria cache L1/memria compartilhada de 64KB, j mencionada, e com 32KB de registradores, compartilhados entre todas as threads que executaro no SM. Uma GPU, portanto, composta basicamente de um conjunto de multiprocessadores (SMs) conectados a uma cache de nvel 2 e a uma memria global. Detalhes desta arquitetura, assim como caractersticas que permitem a execuo concorrente de diferentes unidades de cdigo (kernels), podem ser verificados pelo desenvolvedor com funes disponveis pela API do CUDA Runtime e pela API do CUDA Driver. O escalonamento de threads feito em grupos de 32 threads que executam em paralelo (warps). Na arquitetura Fermi cada multiprocessador tem dois escalonadores de warps e duas unidades de despacho de instrues, possibilitando que dois warps possam ser despachados e executados concorrentemente (dual warps) (NVIDIA Corporation, 2009).

Figura 7.7: Fermi Dual Warp (NVIDIA Corporation, 2009)

O escalonador seleciona dois warps, como estes so independentes possvel despachar uma instruo de cada warp para o grupo de 16 cores, sem a preocupao de dependncias entre as instrues, o que melhora o desempenho. A Figura 7.7 apresenta o esquema de escalonamento da arquitetura Fermi. 7.4.2. Arquitetura Kepler A Arquitetura Kepler (NVIDIA Corporation, 2012b) a nova arquitetura implementada nas GPUs da NVIDIA. Para a Kepler GK110 as caractersticas mais importantes esto relacionadas s melhorias nos multiprocessadores, paralelismo dinmico e a tecnologia Hyper Q.

As GPUs com implementaes mais completas da arquitetura trazem um conjunto de at 15 multiprocessadores (SMX) com 192 ncleos cada. A organizao destes ncleos em um multiprocessador pode ser vista na Figura 7.8.

Figura 7.8: Multiprocessador (SMX) Kepler (Fonte: NVIDIA, 2012b).

Essa nova organizao de mais autonomia GPU, como o conceito de Paralelismo Dinmico introduzido pela arquitetura Kepler, a GPU pode dinamicamente disparar a execuo de novas threads, sincronizar resultados e controlar o escalonamento, adaptando-se ao fluxo de execuo sem a necessidade de envolver o programa executado na CPU do host. Esta independncia, possibilita que mais de um programa possa ser executado diretamente na GPU, e ainda que kernels possam fazer chamadas a outros kernels criando os recursos necessrios para a execuo deles, independente do cdigo executado no host. Esta ideia de paralelismo dinmico pode ser vista na Figura 7.9, onde so comparados os modelos de execuo de threads nas arquiteturas Fermi e Kepler. O comparativo do modelo de execuo apresentado na Figura 7.9, mostra a autonomia dada s GPUs da arquitetura Kepler. Enquanto que nas arquiteturas anteriores existia uma dependncia do cdigo principal executado no host, para

sincronizar dados e disparar novos kernels, na arquitetura Kepler uma chamada uma funo kernel pode gerar novas chamadas a outros kernels feitas pela prpria GPU, que se adapta ao fluxo de execuo.

Figura 7.9: Paralelismo Dinmico (Fonte: NVIDIA Corporation, 2012b)

Figura 7.10: Kepler Quad-Warp (Fonte: NVIDIA Corporation, 2012b)

A tecnologia Hyper-Q possibilita que diferentes threads do host possam disparar a execuo de kernels simultaneamente, na Kepler GK110 so possveis 32 conexes simultneas com os cores da CPU, contra 1 conexo das arquiteturas anteriores (NVIDIA Corporation, 2012b). A proposta que a GPU possa ser utilizada na acelerao de programas escritos para plataformas de troca de mensagens, com MPI, cujos processos podero disparar a execuo simultnea de kernels na GPU, melhorando o desempenho dessas aplicaes. Quanto ao escalonamento, na arquitetura Kepler cada SMX possui quatro escalonadores de warps e oito unidades de despacho de instrues, possibilitando que quatro warps (4 x 32 threads paralelas) possam ser escalonados e executados concorrentemente (quad warp scheduler). A organizao de cada um dos escalonadores mostrado na Figura 7.10, cada um deles com duas unidades de despacho de instrues. 7.4.3. Deteco do dispositivo instalado Para verificar as caractersticas dos dispositivos instalados em um host, a API do CUDA fornece funes que recuperam as propriedades da GPU. A Figura 7.11 apresenta a sada da execuo do exemplo deviceQuery disponvel com o kit de desenvolvimento CUDA, o cdigo procura por dispositivos instalados no host que tenham suporte a CUDA, no caso GPUs e ento lista suas propriedades.
$ ./deviceQuery [deviceQuery] starting...

./deviceQuery Starting... CUDA Device Query (Runtime API) version (CUDART static linking)

Found 1 CUDA Capable device(s)

Device 0: "GeForce GTX 260" CUDA Driver Version / Runtime Version CUDA Capability Major/Minor version number: Total amount of global memory: (27) Multiprocessors x ( GPU Clock rate: Memory Clock rate: Memory Bus Width: Max Texture Dimension Size (x,y,z) 3D=(2048,2048,2048) Max Layered Texture 2D=(8192,8192) x 512 Size (dim) x layers 65536 bytes 16384 bytes 8) CUDA Cores/MP: 4.2 / 4.2 1.3 895 MBytes (938803200 bytes) 216 CUDA Cores 1242 MHz (1.24 GHz) 999 Mhz 448-bit 1D=(8192), 2D=(65536,32768), 1D=(8192) x 512,

Total amount of constant memory: Total amount of shared memory per block:

Total number of registers available per block: 16384

Warp size: Maximum number of threads per multiprocessor: Maximum number of threads per block: Maximum sizes of each dimension of a block: Maximum sizes of each dimension of a grid: Maximum memory pitch: Texture alignment: Concurrent copy and execution: Run time limit on kernels: Integrated GPU sharing Host Memory: Support host page-locked memory mapping: Concurrent kernel execution: Alignment requirement for Surfaces: Device has ECC support enabled: Device is using TCC driver mode: Device supports Unified Addressing (UVA): Device PCI Bus ID / PCI location ID: Compute Mode:

32 1024 512 512 x 512 x 64 65535 x 65535 x 1 2147483647 bytes 256 bytes Yes with 1 copy engine(s) Yes No Yes No Yes No No No 1 / 0

< Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 4.2, CUDA Runtime Version = 4.2, NumDevs = 1, Device = GeForce GTX 260 [deviceQuery] test results... PASSED

> exiting in 3 seconds: 3...2...1...done!

Figura 7.11: Sada da execuo do deviceQuery (Fonte: CUDA SDK, 2012)

Pelas propriedades listadas na Figura 7.11 podemos verificar as caractersticas e capacidades da GPU disponvel. Caractersticas como a quantidade de memria, a quantidade de multiprocessadores, a quantidade de cores por multiprocessador, a quantidade de threads paralelas em execuo (warp size), quantidades mximas de threads por multiprocessador, por bloco, dimenses do grid, dimenses do bloco e se a GPU suporta execuo concorrente de kernels. Outra caracterstica importante a capacidade de computao (compute capability), que basicamente faz um mapeamento entre o hardware da placa e a plataforma de desenvolvimento CUDA. No exemplo da Figura 7.11 a capacidade computacional 1.3. O manual de desenvolvimento CUDA (NVIDIA, 2012b) indica quais so as caractersticas disponveis para esta capacidade de computao, assim como apresenta uma tabela comparativa entre as capacidades.

7.5.

OpenMP: threads na CPU

O objetivo desta seo descrever as principais diretivas do conjunto OpenMP (Open Multi-Processing) (Chapman, 2008), ressaltando o uso de threads em arquiteturas multicore. Antes de descrever o modelo de programao em aceleradores do tipo GPU importante descrever o modelo de programao paralelo das CPUs tradicionais, salientando que estes podem ser combinados. O processamento paralelo na mquina hospedeira utilizando CPU j bem antigo, indo das threads do padro POSIX at bibliotecas que exploram tcnicas de memria compartilhada. A interface pthreads (POSIX - Portable Operating System Interface for UNIX), por exemplo, oferece suporte para a criao e sincronizao de threads. O OpenMP possui vantagens como a facilidade de programao e tem se tornado a alternativa mais popular para programao paralela de alto desempenho em SMPs. Por este motivo foi escolhida para ilustrar a programao multicore neste texto. O padro composto por um pequeno conjunto de diretivas de programao mais um pequeno conjunto de funes de biblioteca e variveis de ambiente que usam como base as linguagens C/C++ e Fortran. Trata-se de um padro, portanto vrias implementaes esto disponveis. comum que compiladores j conhecidos, como o prprio gcc, possuam opes de compilao para OpenMP. 7.5.1. Modelo de execuo Um processo em execuo composto por segmentos de cdigo, dados, pilha e heap (rea de memria livre), no mnimo. Cada processo contm uma thread, que composta basicamente por um contexto (estado do hardware, informaes ao SO), o cdigo e uma pilha. Se o processo tiver mais de uma thread, todas elas compartilham os segmentos de cdigo, dados e heap. Embora o cdigo seja compartilhado, cada thread pode executar uma linha de cdigo diferente, pois cada uma tem seu prprio contexto. O segmento de pilha tambm no compartilhado, o que permite que as threads mantenham seus dados locais. Uma thread normalmente criada a partir do endereo de um procedimento que passado como ponto de partida para sua execuo (exemplo: pthreads). Em OpenMP, porm, as threads so criadas a partir de fragmentos de cdigo (blocos) anotados que normalmente correspondem a alguma tarefa iterativa. O OpenMP se encarrega de criar o procedimento executado pelas threads. Este procedimento tambm chamado de closure, pois tem acesso a variveis declaradas no escopo externo. No incio do processamento h uma nica thread (master thread) que executa sequencialmente at encontrar a primeira construo que delimita uma regio paralela (parallel region). Uma operao similar a um fork acontece ento, fazendo com que um time de threads seja criado para executar o bloco que representa a regio paralela (a thread mestre participa do time). Quando o time de threads termina de executar todos os comandos do bloco acontece uma operao semelhante ao join que sincroniza e finaliza todas as threads criadas e permite que apenas a thread mestre avance. A Figura 7.12 demonstra este comportamento atravs de duas regies paralelas (um programa OpenMP pode ter quantas regies paralelas quantas forem necessrias).

Figura 7.12: Modelo fork-join usado pelo OpenMP (Fonte: Barney, 2012)

7.5.2. Modelo para as diretivas em C/C++ As diretivas em C/C++ para OpenMP esto contidas em diretivas do tipo #pragma, que so dirigidas ao pr-processador, mais a palavra chave omp juntamente com o nome da diretiva OpenMP. O formato genrico para uma diretiva OpenMP em C/C++ pode ser visto na Tabela 7.1.

Tabela 7.1: Formato de uma diretiva OpenMP (Fonte: Barney, 2012)

#pragma omp Obrigatrio para todas as diretivas OpenMP C/C++.

Diretiva Diretiva OpenMP vlida.

Atributos (clusulas) Campo opcional. Os atributos podem aparecer em qualquer ordem.

Nova linha Obrigatrio. Precede uma estrutura de bloco.

A principal diretiva do OpenMP a parallel. Esta diretiva identifica uma regio de cdigo (bloco) que ser executada por mltiplas threads. A Figura 7.13 mostra um exemplo de programa em OpenMP onde se pode observar o uso da diretiva parallel combinada com a diretiva for, que paraleliza automaticamente o lao que vem em seguida. Algumas clusulas tambm foram utilizadas no exemplo e sero explicadas a seguir. O exemplo utiliza valores reduzidos para que a sada possa ser visualizada e analisada com maior facilidade. Na prtica, a carga de trabalho das threads deve ser maior para que o paralelismo seja melhor explorado.

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15

#include <omp.h> //include necessrio em programas OpenMP #include <stdio.h> #define SIZE 16 int main() { int A[SIZE], i;
//inicia as threads que executaro o bloco "for"

#pragma omp parallel for schedule(static, 2) num_threads(4) for(i = 0; i < SIZE; i++){ A[i] = i * i; printf("Th%d[%d] = %d\n", omp_get_thread_num(), i, A[i]); } } Figura 7.13: Exemplo de programa OpenMP: criao de um vetor de quadrados.

Na Figura 7.13 podem ser identificadas algumas das principais diretivas e funes de OpenMP. So elas: incluso do cabealho OpenMP (linha 01); uso da principal diretiva OpenMP, a parallel (linha 9), que automaticamente cria as threads que executaro o bloco de instrues (o lao for, no caso); o a clusula num_threads est associada diretiva parallel e serve para definir a quantidade de threads que ser lanada; o caso ela no seja usada, a quantidade de threads ser a definida pela varivel de ambiente OMP_NUM_THREADS (normalmente ela associada quantidade de ncleos da mquina); o outra alternativa utilizar a funo omp_set_num_threads() antes da primitiva parallel. de biblioteca

uso da diretiva for (linha 9) automaticamente divide a execuo do lao descrito logo em seguida no cdigo (linha 10); o a clusula schedule indica o tipo de escalonamento que ser realizado entre as threads: os ndices podem ser atribudos de forma esttica (static) ou dinmica (dynamic). A tabela 2 mostra dois exemplos de execuo com o tamanho da tarefa (chunk) igual a 2. o uso da funo omp_get_thread_num()(linha 12) retorna o identificador da thread chamadora entre o time de threads que foram iniciadas pela diretiva parallel todas so numeradas de 0 a num_threads -1.

Tabela 7.2: efeito das opes static e dynamic no exemplo da Figura 7.13.

Exemplo de execuo: static Th0[0] = 0 Th0[1] = 1 Th0[8] = 64 Th0[9] = 81 Th1[2] = 4 Th1[3] = 9 Th1[10] = 100 Th1[11] = 121 Th2[4] = 16 Th2[5] = 25 Th2[12] = 144 Th2[13] = 169 Th3[6] = 36 Th3[7] = 49 Th3[14] = 196 Th3[15] = 225

Exemplo de execuo: dynamic Th1[0] = 0 Th0[2] = 4 Th2[4] = 16 Th3[6] = 36 Th1[1] = 1 Th0[3] = 9 Th2[5] = 25 Th3[7] = 49 Th1[8] = 64 Th0[10] = 100 Th2[12] = 144 Th3[14] = 196 Th1[9] = 81 Th0[11] = 121 Th2[13] = 169 Th3[15] = 225

A Tabela 7.2 apresenta duas sadas para a execuo do exemplo da Figura 7.13. Em primeiro lugar, necessrio observar que as threads concorrem por uma srie de recursos da mquina, entre eles o console de sada. Assim, no possvel afirmar que a ordem em que as impresses aparecem na tela a mesma ordem de execuo, mesmo porque numa mquina multicore temos paralelismo real e as instrues podem ser executadas exatamente ao mesmo tempo. Porm, o console um s e teremos concorrncia pelo seu uso, o que d um efeito bagunado sada do programa. Alm disso, a cada execuo podemos ter impresses (e execues) em ordens diferentes. Na primeira coluna da Tabela 7.2 temos a sada de uma execuo do parallel for com a opo static para a clusula schedule. Neste exemplo, o chunk (segundo parmetro da clusula schedule) foi definido com o valor 2. Isto significa que os ndices sero distribudos em pacotes de dois elementos para cada thread. O chunk pode ser definido como sendo o tamanho da tarefa de cada thread. Nesta coluna, podemos observar o tipo de escalonamento round robin empregado na opo static: o escalonamento feito atribuindo-se um pacote de dois elementos para cada thread de forma circular na ordem dos identificadores das threads. A primeira rodada de entrega est destacada em negrito na primeira coluna. A thread 0 (Th0) recebe o primeiro pacote com os ndices 0 e 1, a thread 1 (Th1) recebe o segundo pacote com os ndices 2 e 3, e assim por diante.

Se o tamanho do chunk no for especificado, ele ser calculado pelo sistema de forma a dividir o total de itens (quantidade de iteraes do lao for) pela quantidade de threads no time. Assim, cada thread receber no mximo um pacote de tarefas ( importante para o balanceamento que a quantidade de iteraes seja divisvel pelo nmero de threads lanadas). Na segunda coluna da Tabela 7.2 temos o exemplo para a opo dynamic da clusula schedule. Neste caso, os chunks de dois elementos foram distribudos durante a execuo s threads prontas para execut-los. O primeiro pacote, no exemplo, foi entregue thread 1 (Th1) ao invs da thread 0 (Th0) como no exemplo anterior (este comportamento foi destacado em negrito na segunda coluna). Nesta opo de escalonamento, caso o tamanho do chunk no seja definido, ele ser considerado como sendo de tamanho 1. A compilao simples. No gcc, por exemplo, basta acrescentar a opo fopenmp ao comando de compilao. A Figura 7.14 mostra um possvel comando de compilao no gcc.

gcc fopenmp quadrados.c o quadrados Figura 7.14: Comando de compilao usando gcc

Ferramentas de programao como o Microsoft Visual Studio 2010 ou o Apple Xcode, por exemplo, possuem opo para ativao do suporte ao OpenMP. Normalmente isto pode ser definido editando-se as propriedades do projeto. 7.5.3. Modelo de memria No exemplo da Figura 7.13 foram criadas quatro threads e todas elas acessam o vetor A. Isto possvel porque as variveis declaradas no escopo onde o bloco paralelo se encontra so automaticamente compartilhadas entre as threads. Porm, se observarmos atentamente o exemplo, veremos que isso no poderia ser aplicado varivel i que usada como ndice no lao for. Caso esta varivel fosse compartilhada, teramos vrias threads tentando escrever em posies sobrepostas no vetor. Isto no acontece no exemplo porque usamos a diretiva for, que transforma automaticamente o ndice do vetor em varivel local que no compartilhada entre as threads. possvel especificar de forma explcita o comportamento das variveis que sero usadas dentro do bloco parallel atravs das clusulas shared e private. Embora no seja necessrio no exemplo, a Figura 7.15 mostra como ficaria a utilizao destas clusulas no cdigo da Figura 7.13 (linha 9).

#pragma omp parallel for schedule(static, 2) num_threads(4) shared(A) private(i) Figura 7.15: Exemplo de uso das clusulas shared e private

Um problema quando se programa com memria compartilhada controlar o uso das variveis compartilhadas que sofrem atualizao a partir de vrias threads. Vamos

supor que ao invs de criar um vetor de quadrados queremos realizar o somatrio de quadrados. O trecho da Figura 7.16 foi executado algumas vezes com SIZE igual a 1000 elementos e durante uma pequena srie de execues foi possvel observar resultados diferentes (errneos) para o somatrio, resultado da falta de proteo para a varivel somatorio. Para realizar este tipo de teste possvel desativar a opo para suporte ao OpenMP a fim de se obter o resultado sequencial, livre de possveis bugs. Para este exemplo, resultado do somatrio dos primeiros 1000 quadrados 332833500 na verso sequencial. Nas quatro primeiras execues paralelas com o OpenMP ativado o resultado foi o mesmo. Porm, j na quinta execuo obteve-se um resultado diferente: 316693566, ou seja, um resultado errado. O programa, desta forma, considerado nodeterminstico: muitas vezes o bug s detectado em situaes muito especficas, tornando o problema difcil de detectar e corrigir.
int somatorio=0, i; #pragma omp parallel for schedule(static,2) num_threads(4) // shared(somatorio) for(i = 0; i < SIZE; i++) somatorio += i * i; printf("somatorio = %d\n", somatorio); Figura 7.16: Varivel compartilhada sem proteo

O OpenMP oferece pelo menos duas maneiras de prevenir esta situao. A primeira a primitiva critical, que cria uma rea de excluso mtua que delimita um bloco de cdigo onde apenas uma thread poder executar de cada vez. Esta primitiva seria correspondente a se colocar um lock no incio do bloco e um unlock no final. A Figura 7.17 ilustra o uso da primitiva critical.
int soma_local, somatorio=0, i; #pragma omp parallel num_threads(4) shared(somatorio) // private(soma_local) { soma_local=0; #pragma omp for schedule(static, 2) for(i = 0; i < SIZE; i++) soma_local += i * i; #pragma omp critical somatorio += soma_local; } Figura 7.17: Exemplo da primitiva critical

Para agilizar o processamento, uma varivel local foi criada. Ela permite que as threads realizem o processamento local de forma independente uma das outras. Ao final deste processamento local, a varivel compartilhada (somatorio) atualizada por uma thread de cada vez. Este tipo de processamento conhecido como operao de reduo (reduction). Quando se tratar de uma diretiva for, entretanto, visto que esta uma operao comum, possvel utilizar a clusula reduction que realiza a operao de reduo de

forma implcita. O resultado ser compartilhado e no necessrio especificar atravs da clusula shared. A Figura 7.18 mostra uma nova verso do cdigo, agora com o uso da clusula reduction.
#pragma omp parallel for schedule(static, 2) reduction(+:somatorio) for(i = 0; i < SIZE; i++) somatorio += i * i; Figura 7.18: Exemplo de uso da clusula reduction

O OpenMP possui ainda uma srie de primitivas e clusulas que podem ser utilizadas para gerenciar a execuo do cdigo paralelo. Uma descrio completa pode ser encontrada em (Chapman, 2008).

7.6.

CUDA: threads na GPU

CUDA (Compute Unified Device Architecture), a tecnologia da NVIDIA introduzida em Novembro de 2006 conhecida como Arquitetura de Computao Paralela de Propsito Geral. CUDA trouxe um novo modelo de programao paralela e um conjunto de instrues, que permitem que aplicaes paralelas sejam executadas em GPUs (Graphics Processing Units) para a soluo de problemas complexos mais eficientemente que em CPU (Central Unit Processing). Este poder de processamento est disponvel aos programadores, porm desenvolver aplicaes de propsito geral para GPU requer um bom conhecimento dos conceitos, ferramentas e capacidades que a tecnologia CUDA fornece. CUDA permite que seus desenvolvedores utilizem C/C++ como linguagem de desenvolvimento. So fornecidas bibliotecas e alguns exemplos de aplicaes pertencentes ao seu kit de desenvolvimento (SDK). Alm de sua API padro, suporta outras linguagens como FORTRAN, OpenCL (KHRONOS, 2010) e DirectCompute (MICROSOFT, 2010) (NVIDIA, 2012) e abordagens baseadas em diretivas para aceleradores como OpenACC (OPENACC, 2011). 7.6.1. Organizao do Modelo de Programao O conceito fundamental da execuo paralela em dispositivos CUDA est associado ao uso de threads para o paralelismo de dados, de granularidade fina. Explora o paralelismo de dados existente para acelerar a execuo das aplicaes, pois uma grande quantidade de operaes podem ser feitas sobre a estrutura de dados do programa simultaneamente (Kirk, 2010). O modelo de programao CUDA assume que suas threads so executadas em um dispositivo separado, uma GPU, que trabalha como coprocessador do host. Desta maneira, o cdigo principal executado na CPU, e faz chamadas a funes que so executadas pela GPU, conforme apresentado na Figura 7.19.

Figura 7.19: Fluxo de execuo

As threads executam todas um mesmo cdigo definido em uma funo denominada kernel. Um kernel um conceito estendido de uma funo C, porm, ao contrrio desta, quando uma chamada a uma funo kernel feita, so executadas N instncias paralelas por N threads CUDA. Uma funo definida como kernel usando-se a declarao __global__ e uma chamada a uma funo kernel distingue-se de uma chamada a uma funo comum pela especificao do nmero de threads que a executaro. A especificao dada usando a notao <<<>>> na chamada. Os valores dentro dos <<<>>> so parmetros para o sistema de runtime que descrevem como deve ser a invocao da funo kernel. O primeiro nmero indica o nmero de blocos paralelos e o segundo indica o nmero de threads por bloco. As threads em execuo recebem um identificador disponvel na varivel threadIdx e acessvel dentro do cdigo do kernel, possibilitando o controle de acesso aos dados por determinadas threads. A Figura 7.20 um exemplo de cdigo, originalmente disponvel em (NVIDIA, 2012), que representa a definio de uma funo kernel para a soma de dois vetores A e B, armazenando os resultados em um vetor C.
01 02 03 04 05 06 07 08 09 // Definio da Funo Kernel. __global__ void VecAdd(float* A, float* B, float* C) { int i = threadIdx.x; C[i] = A[i] + B[i]; } int main() { ... // Invocao do Kernel com N threads. VecAdd<<<1, N>>>(A, B, C); } Figura 7.20: Exemplo de kernel (Fonte: NVIDIA, 2012a)

O modelo de programao CUDA utiliza-se de uma estrutura, ou forma de organizar o contexto de execuo, em: grids, blocos e threads, formando uma hierarquia de threads. Neste modelo, as threads so agrupadas em blocos e estes so agrupados em um grid, o que resulta em uma funo kernel sendo executada como um grid de blocos de threads. Essa hierarquia de threads mapeada para a hierarquia de processadores na GPU. As GPUs atuais executam um ou mais grids de kernels, enquanto um streaming multiprocessor (SM na Fermi ou SMX na Kepler) executa um ou mais blocos de threads e os cores e outras unidades de execuo no SMX executam as instrues da thread (NVIDIA Corporation, 2012b). Variveis embutidas (blockId.x, blockId.y, threadId.x e threadId.y, por exemplo) so definidas pelo runtime do CUDA para possibilitar a manipulao do arranjo estrutural de threads montado com a chamada funo kernel. Estes identificadores esto disponveis durante a execuo, possibilitando tanto o chaveamento no cdigo quanto que decises sejam tomadas, podendo o fluxo de execuo ser alterado com base nos ids das threads. possvel identificar qual bloco est sendo executado correntemente no dispositivo. CUDA permite definirmos um grupo de blocos, em at trs dimenses, para problemas com domnios de dimenses variadas, tal como uma matriz ou uma imagem (bidimensionais, nestes casos). Quando lanamos a execuo de um kernel, especificamos um nmero de blocos paralelos. Esse conjunto de blocos paralelos chamado de grid. Percebe-se que este no um modelo trivial de programao, portanto ser apresentado atravs de exemplos de cdigo e figuras demonstrando a acomodao das threads em diferentes dimenses. Supondo que quisssemos adicionar um valor passado por parmetro, a cada elemento de um arranjo de tamanho N, poderamos utilizar a funo kernel definida na Figura 7.21.
01 // Definio da funo kernel. 02 __global__ void addValue(float *vector, float value, int n){ 03 04 05 06 07 08 } Figura 7.21: Exemplo de organizao do arranjo de threads // Recupera o Id global da thread. int id = blockIdx.x * blockDim.x + threadIdx.x; // Garantia de que o id est nos limites do arranjo. if (id < n) vector[id] += value;

A linha 04 recupera o id da thread dentro do arranjo formado pelo grid e pelos blocos de threads, e na linha 07 o valor adicionado ao elemento do arranjo que assistido pela thread . No cdigo principal, com as chamadas funo kernel exemplificadas na Figura 7.22 possvel perceber que existem pelo menos duas formas de estruturar o arranjo de threads e blocos. Na primeira forma (linha 02), especificamos ao runtime um grid

unidimensional com N blocos, e cada bloco com uma thread, na segunda (linha 03) declaramos um grid com 1 bloco com N threads.

01 // Restante do cdigo suprimido. 02 addValue<<<N, 1>>>(d_vector, value, N); 03 addValue<<<1, N>>>(d_vector, value, N); Figura 7.22: Chamadas funo kernel

Uma chamada a uma funo kernel cria um grid de blocos de threads, as quais executam o cdigo. Os identificadores threadIdx.x e threadIdx.y so identificadores associados a cada thread dentro de um bloco. Na chamada da linha 02 especificamos um grid com N blocos, cada bloco com uma thread, desta forma os valores de blockIdx.x iro de 0 a N-1. Se N for igual a 4, por exemplo, os blocos so referenciados conforme a Figura 7.23.

Figura 7.23: Bloco e seus identificadores

Uma forma mais genrica de especificar o arranjo de threads apresentada na Figura 7.24, calculando-se o tamanho do grid em funo do tamanho de bloco. Neste trecho de cdigo o arranjo de threads, ser preparado para trabalhar sobre uma estrutura de 64 elementos. A varivel gridSize, na linha 08, ir receber o valor 8 pelo resultado de 64/8.
01 02 03 04 05 06 07 08 09 10 11 #define N 64 // Restante do cdigo suprimido. int blockSize, gridSize; // Nmero de threads em cada bloco. blockSize = 8; // Nmero de blocos de threads no grid. gridSize = (int)ceil((float)N/blockSize); // Execute the kernel addValue<<<gridSize, blockSize>>>(d_vector, value, N); Figura 7.24: Chamadas funo kernel

A chamada funo kernel (linha 11) ser addValue<<<8,8>>>(d_vector, value, N). Assim teremos um grid formado por 8 blocos de 8 threads cada, conforme a Figura 7.25.

Figura 7.25: Disposio de 64 threads em um grid (8x8)

Figura 7.26: Traduo para endereamento linear

As dimenses para um grid so limitadas a 65535 x 65535 x 1, bem como o nmero de threads em um bloco no pode ser excedido, o campo maxThreadsPerBlock do registro de informaes do dispositivo que pode ser

recuperado chamando a funo cudaGetDeviceProperties(...). Para muitos dispositivos esse nmero de 512 threads por bloco. Para problemas que envolvam estruturas de dados (vetores e matrizes) que ultrapassem essas dimenses, pode-se utilizar uma combinao de blocos e threads em um grid. Com um arranjo estrutural de computao com mltiplos blocos e mltiplas threads, a indexao ser anloga ao mtodo para converses entre espao bidimensional e o espao linear, conforme apresentado na Figura 7.26. 7.6.2. Gerenciamento de Memria Como GPU e CPU possuem memrias separadas, a memria principal espao de endereamento da CPU e a GPU possui sua prpria memria. possvel gerenciar a memria da GPU pelo cdigo em execuo no host (CPU), funes similares s funes j conhecidas dos programadores em linguagem C so fornecidas para que a alocao e liberao de memria do dispositivo possa ser feita. possvel tambm copiar dados do host para o dispositivo, do dispositivo para o host e de dispositivo para dispositivo. 7.6.2.1 Alocao de memria na GPU Regies de memria destinadas a variveis ou estruturas de dados podem ser alocadas no espao de endereamento da GPU usando-se a funo cudaMalloc(). E podem ser inicializadas com um valor padro usando-se a funo cudaMemset(), aps o uso essa memria pode ser liberada com chamadas funo cudaFree(). A transferncia de dados entre as memrias do host e do dispositivo so feitas utilizandose a funo cudaMemcpy(), a direo da cpia dada pelos tipos: cudaMemcpyHostToHost (host para host), cudaMemcpyHostToDevice (host para dispositivo), cudaMemcpyDeviceToHost (dispositivo para host) e cudaMemcpyDeviceToDevice (dispositivo para dispositivo). A Figura 7.27 apresenta as funes de manipulao de memria.
cudaMalloc(void **pointer, size_t nbytes) cudaMemset(void *pointer, int value, size_t count) cudaMemcpy(void dst, const void src, size_t cudaMemcpyKind kind) cudaFree(void *pointer) Figura 7.27: Funes de Manipulao de Memria

count,

enum

A Figura 7.28 na linha 05 exemplifica a alocao de memria para um arranjo no host, as linhas 06 e 07 fazem a alocao e inicializao da estrutura na memria da GPU. No mesmo trecho de cdigo feita cpia do arranjo da memria do host para a memria da GPU, e no final as funes de liberao de memria, tanto na GPU (linha 11), quanto no host (linha 12).
01 02 03 04 05 int int int int h_a n = 1024; nbytes = 1024 * sizeof(int); *h_a = 0; *d_a = 0; = (int*) malloc(nbytes); // Alocao de Memria no host.

06 07 08 09 10 11 12

cudaMalloc((void**)&d_a, nbytes); // Alocao de Memria da GPU. cudaMemset( d_a, 0, nbytes); // Inicializao da Memria alocada na GPU. cudaMemcpy(d_a, h_a, nbytes, cudaMemcpyHostToDevice); // Cpia host -> GPU. cudaFree(d_a); // Liberao de Memria na GPU. free(h_a); // Liberao de Memria no host. Figura 7.28: Alocao e cpia de um array para a GPU

7.6.3. Exemplo: Soma de Vetores Seguindo os conceitos apresentados nas sees anteriores, a Figura 7.29 traz um exemplo completo, uma verso para CUDA do algoritmos de soma de vetores. Este exemplo baseado em um tutorial oferecido pelo OLCF (Oak Ridge Leadership Computing Facility), um dos maiores centros de processamento de alto desempenho do mundo (OLCF, 2012).
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 #include <stdio.h> #include <stdlib.h> #include <math.h> // CUDA kernel. Cada thread responsvel por um elemento de c. __global__ void vecAdd(float *a, float *b, float *c, int n) { // Recupera o ID global da thread. int id = blockIdx.x*blockDim.x+threadIdx.x; // Verificao se o id est dentro dos limites. if (id < n) c[id] = a[id] + b[id]; } int main( int argc, char* argv[] ) { // Tamanho dos vetores. int n = 100000; // Declarao do vetores de entrada do Host. float *h_a; float *h_b; // Declarao do vetor de sada do Host. float *h_c; // Declarao dos vetores de entrada na memria da GPU. float *d_a; float *d_b; // Declarao do vetor de sada do dispositivo. float *d_c; // Tamanho em bytes de cada vetor. size_t bytes = n*sizeof(float); // Alocao de memria para os vetores do host. h_a = (float*)malloc(bytes); h_b = (float*)malloc(bytes);

39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88

h_c = (float*)malloc(bytes); // Alocao de memria para cada vetor na GPU. cudaMalloc(&d_a, bytes); cudaMalloc(&d_b, bytes); cudaMalloc(&d_c, bytes); int i; // Inicializao dos vetores do host. for( i = 0; i < n; i++ ) { h_a[i] = sinf(i)*sinf(i); h_b[i] = cosf(i)*cosf(i); } // Cpia dos vetores do host para o dispositivo. cudaMemcpy( d_a, h_a, bytes, cudaMemcpyHostToDevice); cudaMemcpy( d_b, h_b, bytes, cudaMemcpyHostToDevice); int blockSize, gridSize; // Nmero de threads em cada bloco de threads. blockSize = 1024; // Nmero de blocos de threads no grid. gridSize = (int)ceil((float)n/blockSize); // Chamada funo kernel. vecAdd<<<gridSize, blockSize>>>(d_a, d_b, d_c, n); // Cpia do vetor resultado da GPU para o host. cudaMemcpy( h_c, d_c, bytes, cudaMemcpyDeviceToHost ); // Soma do vetor c e impresso do resultado dividido por n, que deve ser igual a 1. float sum = 0; for(i=0; i<n; i++) sum += h_c[i]; printf("Resultado Final: %f\n", sum/n); // Liberao da memria da GPU. cudaFree(d_a); cudaFree(d_b); cudaFree(d_c); // Liberao da memria do host. free(h_a); free(h_b); free(h_c); return 0; } Figura 7.29: Soma de Vetores. Fonte: (OLCF, 2012)

7.7.

OpenCL: biblioteca de programao heterognea

OpenCL (Munchi, 2011) possui uma filosofia ligeiramente diferente de CUDA. Em OpenCL a linguagem e seu sistema de tempo de execuo servem como uma camada de abstrao ao hardware heterogneo. Assim, um programa OpenCL tem o objetivo de aproveitar todos os dispositivos presentes na mquina, tais como processadores

multicore, GPUs, DSPs (Digital Signal Processors), entre outros. Este captulo apresenta rapidamente a linguagem e seu ambiente de execuo e baseado no texto apresentado em (Silva, 2012). Uma aplicao OpenCL que executa num hardware heterogneo deve seguir os seguintes passos: Descobrir os componentes que compem o sistema heterogneo; Detectar as caractersticas do hardware heterogneo tal que a aplicao possa se adaptar a elas; Criar os blocos de instrues (kernels) que iro executar na plataforma heterognea; Iniciar e manipular objetos de memria; Executar os kernels na ordem correta e nos dispositivos adequados presentes no sistema; Coletar os resultados finais. Estes passos podem ser realizados atravs de uma srie de APIs do OpenCL juntamente com um ambiente de programao e execuo dos kernels. Esta seo apresenta um resumo do modelo de abstrao do OpenCL juntamente com um exemplo simples de cdigo. O modelo de abstrao apresentado atravs de trs modelos fundamentais descritos a seguir: modelo de plataforma, modelo de execuo e modelo de memria. 7.7.1. Modelo de plataforma O modelo de plataforma composto por um host e um ou mais dispositivos OpenCL (OpenCL devices). Cada dispositivo possui uma ou mais unidades de computao (compute units), que por sua vez so compostos por um conjunto de elementos de processamento (processing elements). A Figura 7.30 apresenta esta organizao.

Figura 7.30: modelo de plataforma do OpenCL (Fonte: Munchi, 2011)

O host conectado a um ou mais dispositivos e responsvel pela inicializao e envio dos kernels para a execuo nos dispositivos heterogneos. Os dispositivos normalmente so compostos por unidades de computao que agrupam uma determinada quantidade de elementos de processamento. Em relao CUDA, as unidades de computao correspondem aos Streaming Multiprocessors da GPU (dispositivo) e os elementos de processamento correspondem aos ncleos (cores). Um

dispositivo, portanto, pode ser uma CPU, GPU, DSP ou outro qualquer, dependendo da implementao do OpenCL. 7.7.2. Modelo de execuo O modelo de execuo define que uma aplicao OpenCL composta por um programa host e um conjunto de kernels. O programa host executa no host (normalmente uma CPU) e os kernels executam nos dispositivos disponveis. O programa host, ou simplesmente host, envia o comando de execuo de um kernel para um dispositivo. Isto faz com que vrias instncias da funo que implementa o kernel sejam executadas no dispositivo alvo. Em OpenCL estas instncias so chamadas de itens de trabalho (work-items) e correspondem s threads de CUDA. Assim como em CUDA, cada thread ou item de trabalho identificado por suas coordenadas no espao de ndices (que aqui tambm pode ter at 3 dimenses) e correspondem ao seu global ID. Os itens de trabalho so organizados, por sua vez, em grupos de trabalho (workgroups). Estes oferecem uma maneira de estabelecer granularidades diferentes aos grupos de itens de trabalho, o que normalmente facilita a diviso de trabalho e a sincronizao. Os grupos de trabalho correspondem aos blocos de CUDA e podem ser situados num espao de at trs dimenses. Assim, os itens de trabalho possuem dois tipos de coordenadas: local (dentro do grupo de trabalho) e global (dentro do conjunto completo de itens de trabalho em execuo). O host deve ainda definir um contexto (context) para a aplicao OpenCL. Um contexto define o ambiente de execuo no qual os kernels so definidos e executam e definido em termos dos seguintes recursos: dispositivos, conjunto de kernels, objetos de programa (cdigos fonte e executvel dos kernels que executam a aplicao) e objetos de memria (dados que sero utilizados pelos kernels durante o processamento). Assim, um contexto todo o conjunto de recursos que um kernel vai utilizar durante sua execuo. O contexto definido em tempo de execuo pelo host de acordo com os dispositivos disponveis na mquina alvo. Para possibilitar uma escolha dinmica do dispositivo onde os kernels vo executar o OpenCL compila os kernels dinamicamente, gerando os objetos de programa, portanto, em tempo de execuo. A interao entre o host e os dispositivos realizada atravs de uma fila de comandos (command-queue). Os comandos so colocados nesta fila e aguardam seu momento de executar. A fila criada pelo host e conectada a um dispositivo logo aps a criao do contexto. Esta fila aceita trs tipos de comandos: execuo de kernel, transferncia de dados (objetos de memria) e comandos de sincronizao. Os comandos colocados em uma fila executam de forma assncrona com relao ao host. Comandos de sincronizao podem ser utilizados caso uma ordem deva ser estabelecida. Os comandos na fila normalmente executam em ordem (in-order execution), porm algumas implementaes de OpenCL podem oferecer o modo de execuo fora de ordem (out-of-order execution), que prev uma execuo assncrona dos comandos enfileirados.

7.7.3. Modelo de memria O modelo de memria do OpenCL define dois tipos de objetos de memria: buffers (blocos contguos de memria aos quais possvel mapear estruturas de dados) e imagens. Estas podem ser manipuladas atravs de funes especficas presentes na API do OpenCL. O modelo de memria define cinco regies diferentes (Figura 7.31): Memria do host (host memory): visvel apenas ao host; Memria global (global memory): permite acesso para leitura e escrita a partir de todos os itens de trabalho em todos os grupos de trabalho; Memria constante (constant memory): uma memria global que inicializada pelo host e permite acesso somente de leitura aos itens de trabalho; Memria local (local memory): compartilhada apenas entre os itens de trabalho de um mesmo grupo de trabalho; Memria privada (private memory): fica no escopo de cada item de trabalho. A interao entre o host e o modelo de memria pode ocorrer de duas maneiras: cpia explcita ou mapeamento de regies de um objeto de memria. Na cpia explcita, comandos de transferncia entre host e dispositivos so enfileirados na fila de comandos e podem ser executados de forma sncrona ou assncrona. No mtodo de mapeamento, os objetos de memria so mapeados na memria do host, que pode tambm realizar acessos a estes objetos. O comando de mapeamento tambm deve ser enfileirado na fila de comandos.

Figura 7.31: Regies de memria de OpenCL (Fonte: Munchi, 2011)

7.7.4. Exemplo: soma de vetores em OpenCL As Figuras 7.32 e 7.33 apresentam um exemplo de soma de vetores em OpenCL. Este exemplo baseado em um tutorial oferecido pelo OLCF (Oak Ridge Leadership Computing Facility), um dos maiores centros de processamento de alto desempenho do mundo (OLCF, 2012). A Figura 7.32 apresenta o cdigo do kernel, que pode ficar num arquivo separado (.cl) ou pode ser formatado no prprio cdigo como uma string-C. Este cdigo ser passado como argumento funo OpenCL que o compilar em tempo de execuo. A partir do cdigo da Figura 7.32 possvel observar algumas caractersticas de OpenCL: A definio de um kernel feita atravs de uma funo que utiliza o modificador __kernel (linha 01); O modificador __global indica que os parmetros esto na memria global do dispositivo (linhas 01 a 03); A funo get_global_id() devolve o identificador global da thread (work item) na dimenso 0 (linha 06); A verificao do identificador (linha 07) comumente realizada neste tipo de computao, pois por motivos de desempenho possvel que threads a mais venham a ser lanadas. A verificao serve para que somente as threads dentro do problema executem o trabalho. Este tipo de verificao tambm comum em CUDA; Na linha 08 a soma realizada (n threads sero iniciadas e cada uma realizar uma soma).

01 02 03 04 05 06 07 08 09

__kernel void vecAdd(__global const __global const __global float const unsigned { int gid = get_global_id(0); if (gid < n) c[gid] = a[gid] + b[gid]; }

float *a, float *b, *c, int n)

Figura 7.32: Kernel em OpenCL (vecAdd.cl)

A Figura 7.33 apresenta a main() juntamente com funes auxiliares do OpenCL que devem ser executadas pelo host. Para reduzir o tamanho do cdigo algumas partes foram omitidas e esto indicadas em comentrios. O cdigo completo pode ser encontrado em (OLCF, 2012).

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54

#include <CL/opencl.h> // Demais includes ... int main( int argc, char* argv[] ) { // Declaraes e inicializaes ...(omitido) // Alocao de memria inicializao para cada vetor no host ...(omitido) // Nmero de itens de trabalho em cada grupo de trabalho local size_t localSize = 64; // Nmero total de itens de trabalho size_t globalSize = ceil(n/(float)localSize)*localSize; /* Obtm a plataforma, obtm o ID do dispositivo (GPU), cria um contexto, cria uma command queue, l o cdigo fonte do kernel e transforma numa string-C */ ...(omitido) // Cria o programa program = clCreateProgramWithSource(context, 1, (const char **) &kernelSource, NULL, &err); // Compila o executvel clBuildProgram(program, 0, NULL, NULL, NULL, NULL); // Cria o kernel kernel = clCreateKernel(program, "vecAdd", &err); // Cria os arrays de entrada e sada na memria do dispositivo d_a =clCreateBuffer(context,CL_MEM_READ_ONLY,bytes,NULL,NULL); d_b =clCreateBuffer(context,CL_MEM_READ_ONLY,bytes,NULL,NULL); d_c =clCreateBuffer(context,CL_MEM_WRITE_ONLY,bytes,NULL,NULL); // Escreve os dados no array de entrada do dispositivo err = clEnqueueWriteBuffer(queue, d_a, CL_TRUE, 0, bytes, h_a, 0, NULL, NULL); err |= clEnqueueWriteBuffer(queue, d_b, CL_TRUE, 0, bytes, h_b, 0, NULL, NULL); // Envia argumentos ao kernel err = clSetKernelArg(kernel, 0, sizeof(cl_mem), &d_a); err |= clSetKernelArg(kernel, 1, sizeof(cl_mem), &d_b); err |= clSetKernelArg(kernel, 2, sizeof(cl_mem), &d_c); err |= clSetKernelArg(kernel, 3, sizeof(unsigned int), &n); // Executa o kernel em todo o intervalo de dados err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL, &globalSize, &localSize,0, NULL, NULL); // Espera que finalizao do kernel antes de ler os resultados clFinish(queue); // L resultados a partir do dispositivo clEnqueueReadBuffer(queue, d_c, CL_TRUE, 0, bytes, h_c, 0, NULL, NULL ); //Teste: Soma o vetor c e imprime o resultado dividido por n libera recursos do OpenCL e a memria do host ...(omitido) return 0; } Figura 7.33: Soma de vetores em OpenCL (Fonte: OLCF, 2012)

//

A seguir, destacamos as principais caractersticas de OpenCL presentes no cdigo: Na linha 12 definido o localSize que a quantidade de itens de trabalho em cada grupo de trabalho 64 neste caso. Isto equivale em CUDA a definir a quantidade de threads em cada bloco; A linha 14 define a quantidade total de itens de trabalho lanados (globalSize). Num primeiro momento pensaramos que este nmero deve ser igual ao tamanho do vetor (n). Porm, globalSize deve ser divisvel por localSize, por isso o arredondamento realizado nesta linha; A partir da linha 16 deve ser realizado o setup do OpenCL (omitido por motivo de espao): plataforma, dispositivo, contexto e fila de comandos (command queue); Entre as linhas 21 e 27 o kernel e compilado e criado; Entre as linhas 29 e 41 os dados so preparados e enviados ao dispositivo; Na linha 43 o kernel enfileirado e por fim lanado no dispositivo; Na linha 46 o host espera a finalizao do kernel (sincronizao); Na linha 48 o resultado lido da memria do dispositivo.

7.8.

OpenACC: diretivas de programao para GPU

O objetivo desta seo descrever as principais diretivas do OpenACC, destacando a possibilidade de heterogeneidade caso ele seja adicionado ao OpenMP devido s suas semelhanas intrnsecas. O OpenACC um padro para programao paralela que foi anunciado em novembro de 2011 na conferncia SuperComputing 2011, em uma parceria entre NVIDIA, Cray Inc., Portland Group (PGI), e CAPS Enterprise. O padro tem como base o compilador PGI desenvolvido pelo Portland Group que descreve uma API de programao que fornece uma coleo de diretivas para especificar laos e regies de cdigo paralelizveis que podem ter sua execuo acelerada por dispositivo tal como uma GPU (OPENACC, 2011) (OPENACC STANDARD, 2011). No OpenACC as transferncias de dados entre as memrias do dispositivo acelerador e do host e caching de dados so implcitos e gerenciados pelo compilador com base em anotaes feitas pelo programador por meio de diretivas do OpenACC. Percebe-se rapidamente as vantagens de se usar o modelo de diretivas em relao ao modelo tradicional de CUDA. O padro ainda no est implementado, mas espera-se uma primeira verso de compilador para meados de 2012. O modelo de execuo do OpenACC tem trs nveis: gang, worker e vector. Esta estruturao para ser mapeada em uma arquitetura organizao em uma coleo de elementos de processamento. Cada um destes elementos multithread e cada thread pode executar instrues vetoriais. No contexto de GPUs um mapeamento possvel poderia ser gang=bloco, worker=warp, vector=threads em um warp (um warp um conjunto de threads escalonveis num multiprocessador (SM)). As diretivas OpenACC em C/C++ so especificadas usando diretivas #pragma,

que um mtodo especificado pelo C padro para fornecer informaes adicionais ao compilador (GCC, 2012), torna-se uma alternativa interessante, pois caso o compilador no reconhea ou no esteja preparado para utilizar esse mecanismo de prprocessamento, essas anotaes sero ignoradas na compilao do cdigo. 7.8.1. Diretivas Cada diretiva em C/C++ sensvel ao caso e inicia com #pragma acc, conforme a Figura 7.34, os tokens seguintes so tratados por uma macro de pr-processamento que aplica as transformaes necessrias declarao imediatamente seguinte, podendo esta ser um bloco ou um lao.
01 #pragma acc directive-name [clause [[,] clause]] new-line Figura 7.34: Formato da diretiva OpenACC

Mais detalhes sobre as diretivas do OpenACC e das clusulas aceitas com cada construo, podem ser obtidos consultando-se o manual disponvel no stio do padro OpenACC (OPENACC STANDARD, 2011). As construes aplicveis como nomes de diretivas so listadas na Tabela 7.3. A Figura 7.35 apresenta um exemplo de utilizao da diretiva parallel na paralelizao de um lao no clculo do valor do nmero PI. Na linha 06 a construo parallel combinada com a construo loop. Algumas combinaes entre as construes so possveis, neste caso a combinao um atalho para a declarao de uma diretiva loop dentro de uma regio paralela e afeta o lao seguinte.

01 02 03 04 05 06 07 08 09 10 11 12 13

#include <stdio.h> #define N 1000000 int main(void) { double pi = 0.0f; long i; #pragma acc parallel loop for (i=0; i<N; i++) { double t= (double)((i+0.5)/N); pi +=4.0/(1.0+t*t); } printf("pi=%16.15f\n",pi/N); return 0; } Figura 7.35: Exemplo Clculo do PI (Fonte: OPENACC, 2012)

Tabela 7.3: Construes para diretivas OpenACC (Fonte: OPENACC, 2011)

Construo

Formato
#pragma acc parallel [clause [[,]clause]] new-line structured block

Significado
Blocos (gangs) de threads (workers) so criados para executar em paralelo a regio demarcada. Uma thread em cada bloco inicia a execuo do cdigo structured block.

parallel

kernels

#pragma acc kernels [clause [[,] Define uma regio que pode ser compilada e transformada em uma clause]] new-line structured block
sequncia de kernels. Tipicamente, cada lao pode ser um kernel distinto, com suas configuraes de blocos e threads. Define variveis escalares, arrays e subarrays para serem alocados na memria da GPU durante a execuo da regio, define ainda se os dados devem ser copiados do host para o dispositivo e se ao trmino da execuo da regio, devem ser copiados de volta. Descreve que tipo de paralelismo deve ser usado na execuo do lao, pode declarar variveis privadas, operaes de reduo e se as iteraes so independentes. Especifica elementos de um array ou um subarray que devem ser mantidos na cache, por exemplo o corpo de um lao. Utilizada para atualizar a mmoria do host copiando os dados do dispositivo e vice-versa. Pode especificar variveis ou arrays que devem ser alocados na GPU durante a regio de dados de uma funo. Faz com que o programa aguarde o trmino de uma atividade assncrona, tal como uma regio de kernels ou a execuo de uma diretiva de update.

#pragma acc data [clause [[,] clause]] new-line

data

structured block

loop

#pragma acc loop [clause [[,] clause]...]new-line for loop

cache update declare

#pragma acc cache( list ) newline #pragma acc update clause [[,] clause]... new-line #pragma acc declare declclause [[,] declclause]... new-line #pragma acc wait [( scalarinteger-expression )] new-line

wait

A sada produzida pelo processo de compilao do cdigo da Figura 7.35 e o resultado produzido pela execuo do programa so apresentadas na Figura 7.36.
$ pgcc -acc -ta=nvidia -Minfo=accel -fast pi.c -o pi main: 6, Generating compute capability 1.3 binary Generating compute capability 2.0 binary 7, Loop is parallelizable #pragma acc loop gang, vector(256) /* blockIdx.x threadIdx.x */ CC 1.3 : 21 registers; 2072 shared, 36 constant, 0 local memory bytes; 50% occupancy CC 2.0 : 19 registers; 2056 shared, 44 constant, 0 local memory

bytes;100% occupancy 9, Sum reduction generated for pi $ ./pi pi=3.141592653589877 Figura 7.36: Sada da compilao e execuo do exemplo PI

O cdigo da Figura 7.37 uma verso modificada de um exemplo (PGI OpenACC, 2012) para a soma de dois vetores. Nota-se que as nicas linhas adicionadas a uma verso sequencial que executaria totalmente no host so as anotaes OpenACC que esto nas linhas 12 e 44. O cdigo anotado no exclui a possibilidade de compilao em um compilador comum, tal como o gcc, pois as diretivas sero pr-processadas, mas no produziro efeito algum.

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42

#include <stdio.h> #include <stdlib.h> /* Assinatura das funes (omitidas) */ void inicializar(float* vetor, int n); void imprimir(float* vetor, int n); void vecaddhost(float *e, float *a, float *b, int n); int comparar(float *r, float *e, int n); /* Funo que ser transformada em kernel */ void vecaddgpu(float *restrict r, float *a, float *b, int n){ #pragma acc kernels for present(r,a,b) for( int i = 0; i < n; ++i ) r[i] = a[i] + b[i]; } /* Programa principal */ int main( int argc, char* argv[] ){ int n; /* tamanho do vetor */ float * a; /* declarao do vetor float * b; /* declarao do vetor float * r; /* declarao do vetor float * e; /* declarao do vetor int i, erros; // Define a quantidade de elementos, default. if( argc > 1 ) n = atoi( argv[1] ); else n = 100000; /* tamanho padro */ if( n <= 0 ) n = 100000; // Alocao dos vetores. a = (float*)malloc( n*sizeof(float) b = (float*)malloc( n*sizeof(float) r = (float*)malloc( n*sizeof(float) e = (float*)malloc( n*sizeof(float) ); ); ); ); parmetro ou valor

1 */ 2 */ de resultado */ de verificao */

// Inicializao dos vetores de entrada. inicializar(a, n); inicializar(b, n);

43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83

/* Execuo na GPU */ #pragma acc data copyin(a[0:n],b[0:n]) copyout(r[0:n]) { vecaddgpu(r, a, b, n); } /* clculo do resultado para comparao */ vecaddhost(e, a, b, n); /* comparar os resultados */ erros = 0; erros = comparar(r, e, n); /* imprimir imprimir(a, imprimir(b, imprimir(r, imprimir(e, os vetores */ n); n); n); n);

printf("Resultado: %d erros encontrados.\n", erros ); return erros; } void vecaddhost(float *e, float *a, float *b, int n){ for( int i = 0; i < n; ++i ) e[i] = a[i] + b[i]; } int comparar(float *r, float *e, int n){ int i, erros = 0; for( i = 0; i < n; ++i ){ if( r[i] != e[i] ){ ++erros; } } return erros; }

Figura 7.37: Exemplo Soma de Vetores com diretivas OpenACC

A diretiva acc data da linha 44, que envolve a chamada funo vecaddgpu no programa principal, faz com que os dados, no caso vetores (a, b) sejam copiados para a memria da GPU e o vetor resultado (r) seja copiado de volta para a memria do host ao final da execuo da regio paralela. A clusula present na funo vecaddgpu diz ao compilador que os dados j esto presentes na GPU, que foram copiados anteriormente. O resultado da compilao apresentado na Figura 7.38. A sada apresentada na Figura 38 mostra as deteces que o compilador pgcc fez em relao s diretivas inseridas no cdigo. Na linha 12 detectou a clusula present que gerou duas verses de cdigo, um para dispositivos com capacidade de computao 1.0 e outro com 2.0 (capacidade de computao ou compute capability faz um mapeamento entre a verso do hardware da placa e a plataforma CUDA). Na linha 13 identificou o lao for como paralelizvel e tambm mostra o escalonamento usado para o lao (gang,

vector(256)), que suas iteraes sero quebradas em blocos de 256 threads que sero executadas em paralelo.
$ pgcc -acc -ta=nvidia,time -Minfo=accel -fast vectoradd-3.c -o vectoradd-3-acc-gpu vecaddgpu: 12, Generating present(b[0:]) Generating present(a[0:]) Generating present(r[0:]) Generating compute capability 1.0 binary Generating compute capability 2.0 binary 13, Loop is parallelizable Accelerator kernel generated 13, #pragma acc loop gang, vector(256) /* blockIdx.x threadIdx.x */ CC 1.0 : 5 registers; 36 shared, 4 constant, 0 local memory bytes; 100% occupancy CC 2.0 : 5 registers; 4 shared, 48 constant, 0 local memory bytes; 100% occupancy main: 44, Generating copyout(r[0:n]) Generating copyin(b[0:n]) Generating copyin(a[0:n]) Figura 7.38: Sada da compilao do exemplo soma vetores

Analisar a sada do compilador importante, pois quando no for possvel paralelizar as regies anotadas, o resultado mostrar dependncias que impedem a paralelizao e dir que cdigo de execuo sequencial foi gerado (#pragma acc loop seq). A ideia de anotar regies e construes que devem ser paralelizadas, trazida pelo padro OpenACC, e implementada pelo compilador do PGI, o pgcc mostra-se bem interessante, pois anotar o cdigo no prejudica a compilao em compiladores no direcionados acelerao, o cdigo compilado normalmente, sendo as diretivas ignoradas.

7.9.

Programao heterognea em outras linguagens

O objetivo desta seo descrever os principais bindings e bibliotecas para acesso aos recursos de CUDA e de GPUs em outras linguagens, tais como Java, Python e C++/STL. A criao de bindings acontece quando existem bibliotecas e se deseja acess-las em uma outra linguagem, mas sem ser necessrio reescrev-las. No nosso caso, acessar recursos das APIs CUDA (NVIDIA, 2012) e OpenCL (KHRONOS, 2011) nas linguagens Java e Python. Alm disso, apresentamos tambm a biblioteca de algoritmos paralelos Thrust, que assemelha-se Standard Template Library (STL) da linguagem C++ (Thrust, 2012). 7.9.1. JCuda e JOCL O JCuda (JCUDA, 2012) e o JOCL (JOCL, 2012) so bindings feitos para linguagem Java, para acessar os recursos das bibliotecas do runtime e do driver CUDA e OpenCL. So APIs que so muito prximas das bibliotecas originais, exceto pelas restries

especficas da linguagem Java, com relao a ponteiros por exemplo, neste caso uma classe Pointer fornecida. A Figura 7.39 apresenta o exemplo da soma de vetores em cdigo equivalente do JCuda. Nota-se que o cdigo faz chamadas a funes do Driver atravs do JCuda, o cdigo bem parecido com o exemplo de utilizao da API do Driver. O cdigo PTX carregado pode ser de um arquivo existente, ou de um obtido pela execuo do comando de compilao com o nvcc e a opo --ptx, por meio do runtime do Java. O cdigo das funes de inicializao, impresso e verificao dos resultados foi omitido.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 // imports ...(omitido) public class JCudaVectorAdd { public static void main(String args[]) throws IOException{ // Tamanho dos vetores. int n = 1024; // Alocao de memria no host para os vetores. // Inicializao dos vetores. // ...(omitido) // Habilitar excees. JCudaDriver.setExceptionsEnabled(true); // Nome do arquivo PTX a ser utilizado. String ptxFileName = "vector_add_kernel.ptx"; // Inicializao da API do Driver. cuInit(0); // Define um handle para o dispositivo 0. CUdevice device = new CUdevice(); cuDeviceGet(device, 0); // Cria um contexto. CUcontext context = new CUcontext(); cuCtxCreate(context, 0, device); // Cria um mdulo de um arquivo ptx. CUmodule module = new CUmodule(); cuModuleLoad(module, ptxFileName); // Alocao dos vetores na memria da GPU. CUdeviceptr dA = new CUdeviceptr(); cuMemAlloc(dA, n * Sizeof.FLOAT); CUdeviceptr dB = new CUdeviceptr(); cuMemAlloc(dB, n * Sizeof.FLOAT); CUdeviceptr dC = new CUdeviceptr(); cuMemAlloc(dC, n * Sizeof.FLOAT); // Cpia dos vetores da memria do host para a memria da GPU. cuMemcpyHtoD(dA, Pointer.to(hA), n * Sizeof.FLOAT); cuMemcpyHtoD(dB, Pointer.to(hB), n * Sizeof.FLOAT); // Cria um manipulador para a funo kernel. CUfunction function = new CUfunction(); cuModuleGetFunction(function, module, "vector_add_kernel");

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73

// Definio dos arranjo de threads. int threadsPerBlock = 16; int blocksPerGrid = (int)Math.ceil((double)(n + threadsPerBlock 1)/threadsPerBlock); // Parmetros para o kernel. Pointer parametros = Pointer.to( Pointer.to(dA), Pointer.to(dB), Pointer.to(dC), Pointer.to(new int[]{n}) ); // Chamada ao kernel. cuLaunchKernel(function, blocksPerGrid, 1, 1, // Dimenso do grid. threadsPerBlock, 1, 1, // Dimenso do Bloco. 0, null, // Memoria compartilhada e a stream.parametros, null // Parmetros. ); // Sincronizao. cuCtxSynchronize(); // Cpia do vetor resultado da GPU para o host. cuMemcpyDtoH(Pointer.to(hC), dC, n * Sizeof.FLOAT); // Impresso e verificao do resultado. // ...(omitido) // Liberao de Memria. cuMemFree(dA); cuMemFree(dB); cuMemFree(dC); } Figura 7.39: Soma de Vetores em JCuda

7.9.2. PyCUDA e PyOpenCL PyCUDA e PyOpenCL (PyCUDA, 2012) so bindings de CUDA e OpenCL para a linguagem Python. A Figura 7.40 apresenta o cdigo do exemplo soma de vetores em PyCUDA.
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 import pycuda.autoinit import pycuda.driver as drv import numpy from pycuda.compiler import SourceModule mod = SourceModule(""" __global__ void vector_add_kernel(float *c, float *a, float *b){ const int id = blockIdx.x * blockDim.x + threadIdx.x; c[id] = a[id] + b[id]; } """) add_func = mod.get_function("vector_add_kernel") a = numpy.random.randn(1024).astype(numpy.float32)

16 17 18 19 20 21 22 23

b = numpy.random.randn(1024).astype(numpy.float32) c = numpy.zeros_like(a) add_func( drv.Out(c), drv.In(a), drv.In(b), block=(16,1,1), grid=(64,1)) print c-(a+b) Figura 7.40: Soma de Vetores em PyCUDA

7.9.3. Thrust Thrust uma biblioteca de algoritmos paralelos que se assemelha Standard Template Library de C++. uma interface de alto nvel com interoperabilidade com outros ambientes de programao tais como CUDA e OpenMP, entre outros. Thrust heterognea e possui uma opo de compilao capaz de alterar a plataforma para a qual o programa compilado (CPU ou GPU): THRUST_DEVICE_SYSTEM. Alm disso, Thrust possui diferentes namespaces que podem ser usados no programa para definir o local de execuo de um algoritmo (exemplos: thrust::omp::vector ou thrust::cuda::vector). O exemplo da Figura 7.41 ilustra brevemente o uso de Thrust que ordena 32M nmeros aleatrios em paralelo numa GPU (Thrust, 2012).

01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

#include #include #include #include #include #include #include

<thrust/host_vector.h> <thrust/device_vector.h> <thrust/generate.h> <thrust/sort.h> <thrust/copy.h> <algorithm> <cstdlib>

int main(void) { // gera 32M nmeros aleatrios sequencialmente thrust::host_vector<int> h_vec(32 << 20); std::generate(h_vec.begin(), h_vec.end(), rand); // transfere os dados para a GPU thrust::device_vector<int> d_vec = h_vec; // ordena os dados na GPU (846M chaves/s numa GeForce GTX 480) thrust::sort(d_vec.begin(), d_vec.end()); // transfere os dados de volta ao host thrust::copy(d_vec.begin(), d_vec.end(), h_vec.begin()); return 0; } Figura 7.41: exemplo da biblioteca Thrust (Fonte: Thrust, 2012)

A Figura 7.41 mostra a utilizao de Thrust de forma muito semelhante ao uso da STL, assim como a programao heterognea combinando a execuo de algoritmos na CPU e na GPU. Na linha 11 inicializado um vector no host (CPU) e na linha 12 ele preenchido com nmeros aleatrios. Na linha 15 uma simples atribuio utilizada para fazer a transferncia do vector do host para o dispositivo (GPU). Na linha 18 o algoritmo de ordenao (sort) executado na GPU e na linha 21 ele copiado para o vector original localizado no host.

7.10. Concluso
Este tutorial apresentou o que tem sido chamado de computao heterognea a partir dos ambientes e ferramentas mais utilizados para a computao combinada em CPU e GPU. Tais dispositivos tem sido referenciados como aceleradores e se juntam a outras opes tais como os FPGAs mencionados no incio do tutorial. Sem dvida, as GPUs so os aceleradores em evidncia no momento e possuem uma srie de opes de ambientes e ferramentas de programao. Este tutorial apresentou algumas das arquiteturas de GPUs mais importantes, tais como a Fermi e a Kepler, assim como abordou ambientes de programao j tradicionais, tais como OpenMP, CUDA e OpenCL, alm de abordagens mais modernas como OpenACC e Thrust. Entretanto, esta uma rea em rpida e constante evoluo. Novas arquiteturas e ambientes de programao so esperados para os prximos anos e este tutorial no encerra todas estas novas possibilidades. Entretanto, existe uma forte tendncia que indica que a programao heterognea recair em modelos de paralelismo do tipo SIMD ou SIMT, que foram amplamente cobertos neste tutorial.

Bibliografia
Barney, B. (2012) OpenMP, Lawrence Livermore National Laboratory, UCRL-MI133316 (disponvel em https://computing.llnl.gov/tutorials/openMP/ , acessado em maio de 2012). Borkar, S.; Chien, A. A. 2011. The future of microprocessors. Commun. ACM 54, 5 (May 2011), 67-77. DOI=10.1145/1941487.1941507. (disponvel em: http://doi.acm.org/10.1145/ 1941487.1941507, acessado em maio de 2012). Boyd, C., Huang, X., Pritchard, C., Kev Gee (2010). DirectCompute: A Teraflop for Everyone. GameFest 2010. Bridges, T. (1990). The gpa machine: a generally partitionable msimd architecture. In Frontiers of Massively Parallel Computation, 1990. Proceedings., 3rd Symposium on the, pages 196203. Brodtkorb, A. R., Dyken, C., Hagen, T. R., & Hjelmervik, J. M. (2010). State-of-the-art in heterogeneous computing. Scientific Programming, 18, 1-33. doi:10.3233/SPR-20090296

Chapman, B.; Jost, G.; Pas, R. Using OpenMP: Portable Shared Memory Parallel Programming (2008). Scientific and Engineering Computation. [S.l.]: The MIT Press, 2008. ISBN 0262533022, 9780262533027. Crawford, C. H., Henning, P., Kistler, M., & Wright, C. (2008). Accelerating computing with the cell broadband engine processor. Proceedings of the 5th conference on Computing frontiers, 3. ACM. Retrieved from http://portal.acm.org/citation.cfm?doid=1366230. 1366234 Darema-Rogers, D. A. G. V. A. N. and Pfister, G. F. (1985). A vm parallel environment. Technical Report RC-11225 (#9161), IBM Thomas J. Watson Research Center. Darema, F. (2001). The spmd model: Past, present and future. In Proceedings of the 8th European PVM/MPI Users Group Meeting on Recent Advances in Parallel Virtual Machine and Message Passing Interface, page 1, London, UK. Springer-Verlag. Dongarra, J.; Lastovetsky, A. High Performance Heterogeneous Computing. WileyInterscience, 1 edition, 2009. Du, P., Weber, R., Luszczek, P., Tomov, S., Peterson, G., Dongarra, J. (2010). From CUDA to OpenCL : Towards a Performance-portable Solution for Multi-platform. Parallel Computing, (UT-CS-10-656), 1-12. Retrieved from http://www.sciencedirect.com/science /article/pii/S0167819111001335 Flynn, M. (1972). Some Computer Organizations and Their Effectiveness. IEEE Trans. Comput., C-21:948+. GCC (2011). Manual do GCC: The C Preprocessor. (disponvel em: http://gcc.gnu.org /onlinedocs/cpp/index.html, acessado em maio de 2012). JOCL (2012) Java Bindings for OpenCL. (disponvel em: http://www.jocl.org/, acessado em maio de 2012). JCUDA (2012) Java Bindings for CUDA. (disponvel em: http://www.jcuda.org/, acessado em maio de 2012). Kam, V. Introducing the DirectCompute Lecture Series!. DirectX Developer Blog. MSDN Blogs. 15 junho. 2010. (disponvel em: http://blogs.msdn.com/b/directx/archive/2010/ 06/15/introducing-the-directcomputelecture -series.aspx, acessado em maio de 2012). Kirk, D. B. and meiW. Hwu,W. (2010). Programming Massively Parallel Processors: A Hands-on Approach. Morgan Kaufmann, 1 edition. Klckner, A., Pinto, N., Lee, Y., Catanzaro, B., Ivanov, P., Fasih, A. (2012) PyCUDA and PyOpenCL: A scripting-based approach to GPU run-time code generation, Parallel Computing, Volume 38, Issue 3, March 2012, Pages 157-174.

KRONOS Group. (2011). OpenCL - The open standard for parallel programming of heterogeneous systems. (disponvel em: http://www.khronos.org/opencl/, acessado em maio de 2012). Kunzman, D. M., & Kal, L. V. (2009). Towards a framework for abstracting accelerators in parallel applications: experience with Cell. Cell, Dijon, Fra(c), 1-12. ACM. Retrieved from http://portal.acm.org/citation.cfm?id=1654114 MUNSHI, A., GASTER, B., MATTSON, T.G., FUNG, J., GISBURG, D. (2011) OpenCL Programming Guide. New York: Addison-Wesley Professional, 2011. NVIDIA Corporation (2009). Fermi Compute Architecture White Paper. Version 1.1. (disponvel em: http://www.nvidia.com/content/PDF/fermi_white_papers/ NVIDIA_Fermi_ Compute_Architecture_Whitepaper.pdf, acessado em maio de 2012). NVIDIA Corporation (2012a). NVIDIA CUDA C Programming guide. Version 4.2. (disponvel em: http://developer.download.nvidia.com/compute/DevZone/docs/ html/C/doc/ CUDA_C_Programming_Guide.pdf, acessado em maro de 2012). NVIDIA Corporation (2012b). NVIDIAs Next Generation CUDA Compute Architecture: Kepler TM GK110 White Paper, The Fastest, Most Efficient HPC Architecture Ever Built. Version 1.0. (disponvel em: http://www.nvidia.com/content/PDF/kepler/NVIDIA-Kepler-GK110-ArchitectureWhitepaper.pdf, acessado em maio de 2012). NVIDIA CUDA SDK (2012) Software Development Kit. Verso 4.2. (disponvel em: http://developer.nvidia.com/cuda-downloads, acessado em maio de 2012). OLCF, Oak Ridge Leadership Computing Facility Tutorial. 2012. (disponvel em: http://www.olcf.ornl.gov/training_articles/opencl-vector-addition/, acessado em abril de 2012). OpenACC (2011a) The OpenACC Application Programming Interface. Version 1.0. November, 2011. (disponvel em: http://www.openacc.org/sites/default/files/ OpenACC.1.0_0.pdf, acessado em maio de 2012). OpenACC Standard. (2011b). (disponvel em: http://www.openacc-standard.org/, acessado em maro de 2012). OpenMP Site (2012). Site do OpenMP. (disponvel em: http://openmp.org, acessado em maio de 2012). PGI OpenACC 2012 Getting Started Guide. The Portland Group, Inc. and STMicroelectronics, Inc. Printed in the United States of America. Release 2012, version 12.4, April 2012. (disponvel em: http://www.pgroup.com/doc/openACC_gs.pdf, acessado em maio de 2012). Silva, L. e Stringhini, D. Paralelizao de Aplicaes para Realidade Aumentada em CUDA e OpenCL. Minicurso apresentado no XIV Symposium on Virtual and Augmented Reality, 28-31 de maio de 2012, Niteri/RJ, Brasil.

Storaasli, O. O., & Strenski, D. (2007). Exploring Accelerating Science Applications with FPGAs. Computing, July. (disponvel em: http://ft.ornl.gov/~olaf/www/pubs/OlafRSSI2July 07.pdf, acessado em maio de 2012). Thrust (2012). Thrust. (disponvel em: http://thrust.github.com/, acessado em maio de 2012). Wolfe, M. (2012) The Heterogeneous Computing Jungle. HPC Wire, Maro, 2012. (disponvel em: http://www.hpcwire.com/hpcwire/2012-0319/the_heterogeneous_program ming_jungle.html, acessado em abril, 2012).

Você também pode gostar