Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
discussions, stats, and author profiles for this publication at: https://www.researchgate.net/publication/266482328
Article
CITATIONS READS
0 843
3 authors, including:
Leandro Zanotto
University of Campinas
5 PUBLICATIONS 0 CITATIONS
SEE PROFILE
Some of the authors of this publication are also working on these related projects:
All content following this page was uploaded by Leandro Zanotto on 15 July 2015.
∗ † ‡
Leandro Zanotto Anselmo Ferreira Marcelo Matsumoto
Instituto de computação Instituto de Computação Instituto de Computação
Universidade Estadual de Universidade Estadual de Universidade Estadual de
Campinas Campinas Campinas
Campinas, São Paulo, Brasil Campinas, São Paulo, Brasil Campinas, São Paulo, Brasil
Em programação de CPUs, costuma-se agrupar dados a //Chamada ao kernel com N threads, o sinal <<<>>> é
serem utilizados próximos temporalmente em estruturas, de //Necessário para marcar a chamada do host (CPU) para
forma que fiquem próximos espacialmente. Por exemplo, //o device (GPU)
se for feita a soma entre dois vetores x e y, armazenando
o resultado em z, cria-se um vetor de estruturas contendo VecAdd<<< 1, N >>>(A,B,C);
o valor de x, y e z dos elementos correspondentes. Já em }
programação de GPUs é aconselhável utilizar o formato orig-
inal dos vetores. Como a soma é executada em paralelo em Os blocos gerados ao chamar um kernel são entregues ao
diversas threads, essa organização permite carregar vários gerenciador dentro de um multiprocessador, sendo distribuı́-
elementos de um único vetor com apenas uma transferência, dos entre os multiprocessadores pelo gerenciador da GPU
diminuindo, dessa forma, o gargalo. para manter o nı́vel de carga igual. Internamente, os blo-
cos ainda são separados em warps, cada um com 32 threads,
Em GPUs, a memória é um gargalo considerável. A tı́- que são controlados pelo gerenciador dentro do multiproces-
tulo de exemplo, na NVIDIA GeForce 285, sete operações sador. Essa hierarquia é mostrada na Figura 6.
de ponto flutuante podem ser executadas por um núcleo de
processamento ao mesmo tempo que um byte demora para
ser transferido da memória externa para a GPU [11]. Por
esse motivo, deve-se organizar a memória de forma que o
acesso possa ser feito através de um único bloco contı́nuo
[12]. A seguir apresentaremos alguns detalhes da abordagem
CUDA.
A variável threadIdx é um componente vetorial com 1, 2 ou Para programar com o CUDA, deve-se seguir alguns coman-
3 dimensões em seu ı́ndice, formando um bloco com 1, 2 dos e passos. A invocação do kernel é, conforme visto no
ou 3 dimensões. Isso fornece uma maneira natural para se Algoritmo 1, utilizando <<<>>>. Depois, definem-se as
chamar os elementos computacionais em um certo domı́nio variáveis como a threadIdx.x ou blockIdx.x, dependendo do
como vetor, matriz ou volume. O ı́ndice da thread e seu que se deseja fazer. Para se alocar a memória na GPU,
threadID se relacionam de uma maneira direta. Para um primeiramente usa-se a alocação da memória no host (CPU)
bloco de uma dimensão, eles são os mesmos. Para blocos de utilizando o comando em C malloc(). Depois, aloca-se a
duas dimensões (Dx, Dy), a thread ID do ı́ndice da thread GPU utilizando o cudaMalloc(). Em seguida, copia-se o que
(x, y) é (x + yDx). Para 3 dimensões ela é (x + yDx + está na CPU para a GPU utilizando o comando cudaMem-
zDxDy). O número de blocos é limitado, já que eles residem cpy(). Assim que essas variáveis forem alocadas, deve-se lib-
no mesmo processador e possuem tamanho de memória lim- erar a memória para seu futuro uso. Em C temos comando
itado. Nas atuais GPUs, o bloco de thread pode conter até free(), e no CUDA, temos o cudaFree(). As threads precisam
2048 threads. Os blocos são organizados em grades com uma ser sincronizadas se estão trabalhando em um bloco e pre-
ou duas dimensões. O número de blocos de threads em uma cisam trabalhar com dados de um iteração anterior. Para
grade é geralmente dito pelo seu tamanho de dados a serem isso, utiliza-se a função synchthreads() para se colocar uma
processados ou pelo número de processadores no sistema no barreira até que todas as threads de um mesmo bloco ter-
qual pode exceder tranquilamente. minem suas tarefas.
Cada thread possui acesso a três nı́veis diferentes de memória, Como visto nesta Seção, CUDA foi uma grande inovação
sendo cada um mais lento que o outro. Em primeiro lugar para a computação paralela, mas foi projetada pela NVIDIA
está a memória privada da thread, que não pode ser compar- somente para as suas GPUs. Porém, também há uma tendên-
tilhada entre threads e representa tanto trechos da memória cia para programação paralela em CPUs através da OpenMP
interna de um multiprocessador quanto registradores. Em que, a partir de comandos simples, possibilita paralelizar um
segundo plano, está a memória compartilhada por bloco. trecho do programa em CPUs multicores. Outra alternativa
Todas as threads em um mesmo bloco possuem acesso a é o OpenCL, que teve sua primeira implementação em GPU
essa memória que fica no multiprocessador, sendo portanto em 2008. O OpenCL possui caracterı́sticas de programação
bastante rápida. No terceiro nı́vel está a memória global, parecidas com CUDA, como a separação em blocos e progra-
que é bastante lenta. Sua utilização deve ser feita somente mação de kernels, mas com as vantagens de ser um padrão
em caso de extrema necessidade. A Figura 7 mostra essa aberto e executar tanto em CPUs quanto em GPUs. Como
hierarquia e seus acessos. o foco deste artigo é apenas a GPGPU com CUDA, mais
detalhes sobre a OPenCL podem ser encontrados em [16].