A EDUCAÇÃO FÍSICA NO NOVO ENSINO MÉDIO: IMPLICAÇÕES E TENDÊNCIAS PROMOVIDAS P...
Introdução ao Processamento Paralelo (4.1)
1. GPGPU
Fontes:
I. David Kirk/NVIDIA and Wen-mei W. Hwu, ECE 408, University of Illinois,
Urbana-Champaign, 2007-2010;
II. David B. Kirk, Wen-Mei W. Hwu, “Programando para Processadores
Paralelos”, Elsevier-Campus, 2012
III. CUDA Programming Guide V.2.3.1, NVIDIA, 2009
1 III Semana Inverno Geofísica
2. Motivação
• Mercado de entretenimento doméstico cresce
rapidamente. Grande volume de pastilhas específicas
para atender o mercado.
• Esse mercado demanda alta qualidade de imagem a
baixo custo
– Requer computação ponto flutuante de alto desempenho
• GPUs (Graphics Processing Units) tornam-se 10 vezes
mais rápidas (Flop/s) que CPUs nas aplicações gráficas
– Talvez possam ser usadas para computação científica
• GPGPUs (General Purpose Graphics Processing Units)
buscam preencher esse espaço
2 III Semana Inverno Geofísica
3. GPU é um acelerador da CPU
GPU CPU
GPU
+
CPU e GPU MEM
atuam sobre
memórias
distintas!
3 III Semana Inverno Geofísica
4. Como acelerar? Ideia Básica 1
• Como acelerar a execução do laço
DO i = 1, n
C(i) = A(i) + B(i)
END DO
• Idéia: um processador para cada iteração
• Todas as iterações executadas simultaneamente
1 2 3 4 5 6 . . . n
• Impossível, pois a quantidade de processadores varia
com n
• Solução: Divide o laço em blocos de tamanho fixo,
mapeados a hardware de tamanho fixo
4 III Semana Inverno Geofísica
6. Ideia Básica 3
• Observação: os 32 processadores executam a mesma
instrução a cada instante
• Então, basta uma unidade de controle e 32 unidades de
execução
• Substitui
P0 P1 P2 P3 P4 P5 . . P31
• Por
controle
cache
unidades de execução
6 III Semana Inverno Geofísica
7. Ideia Básica 4
• Denomine “Streaming Multiprocessor” esse conjunto
• Nomenclatura NVIDIA
• Replique o conjunto
• Para acelerar o laço, use um único SM em instantes sucessivos
ou múltiplos SM simultaneamente
7 III Semana Inverno Geofísica
8. Arquitetura da GPU NVIDIA
SM0
SM1
SM31
DRAM
8 III Semana Inverno Geofísica
9. Programação
• Qualquer laço pode ser executado na GPU?
• Não
• As iterações do laço tem que ser independentes!
• Ex: Laço com iterações dependentes:
for (i=1; i<10; i++) {
b[i] = func(i);
a[i] = b[i-1] + b[i];
}
9 III Semana Inverno Geofísica
10. Programação
• Solução: quebra o laço em laços com iterações
independentes
// executa na gpu:
for (i=1; i<10; i++)
b[i] = func(i);
// espera resultado do anterior e executa na gpu
for (i=1; i<10; i++)
a[i] = b[i-1] + b[i];
10 III Semana Inverno Geofísica
11. Programação
• Cada iteração do laço é denominada uma thread
• O laço tem que ser “desmontado” em um grupo de
threads:
for (i=0; i<n; i++)
c[i] = a[i] + b[i]
• Na função
__global__ void VAdd(float* A, float* B, float* C)
{
int i = threadIdx.x;
c[i] = a[i] + b[i];
}
• E na invocação
VAdd <<< dimGrid, dimBlk >>> (A_d, B_d, C_d);
11
De alguma forma, passa n III Semana Inverno Geofísica
12. Arquitetura NVIDIA
• A GPU é composta por um conjunto de “Streaming
Multiprocessors” (SM)
– Cada SM possui uma unidade de controle e um programa em
execução
– O conjunto das SMs na GPU forma uma máquina MIMD de
memória central – todas as SM vêem a mesma memória
• Cada SM recebe um bloco de até 512 threads para
execução
– Cada thread possui seu conjunto de registradores
• A SM divide o bloco de threads em sub-blocos de 32
threads denominados “warp”
12 III Semana Inverno Geofísica
13. Multithreading SIMD
• A cada ciclo o SM escolhe, no conjunto de warps
prontas para execução, uma warp a executar
– Chavear entre warps a cada ciclo esconde a latência das
demais unidades da GPU (ALUs, Memória, etc)
– Enquanto uma warp é atendida, outras estão em execução ou
prontas para serem escolhidas
• A próxima instrução da warp escolhida é executada
– Executa simultaneamente apenas nas threads da warp cuja
próxima instrução a executar for a instrução enviada para
execução
13 III Semana Inverno Geofísica
14. Escalonamento de Blocos de Threads
O número de SMs por GPU varia com o
modelo de GPU
– Alocação dinâmica de blocos de threads a
SMs
Um modelo Blocos de Threads
Outro modelo
SM SM Bloco 0 Bloco 1
SM SM SM SM
Bloco 2 Bloco 3
Bloco 0 Bloco 1 Bloco 4 Bloco 5
Bloco 0 Bloco 1 Bloco 2 Bloco 3
Bloco 6 Bloco 7
Bloco 2 Bloco 3
time
Bloco 4 Bloco 5 Bloco 6 Bloco 7
t
Bloco 4 Bloco 5
Cada bloco pode executar em qualquer ordem
t Bloco 6 Bloco 7 com relação aos outros blocos
14 III Semana Inverno Geofísica
16. CUDA
• Compute Unified Device Architecture – É um modelo de
programação e uma linguagem para programar CPU +
aceleradores
– CUDA é propriedade da NVIDIA. Perde-se portabilidade
– CUDA é uma extensão de C/C++
– Há CUDA Fortran (implementado pelo compilador PGI)
• A GPU é um dispositivo escravo da CPU
– A CPU envia dados e computação para a GPU e recebe
resultados
– A CPU comanda a computação
• CPU e GPU cooperam na execução da computação
– Controlados pela CPU
– Códigos a executar na GPU são funções denominadas kernels
– CPU envia dados, dispara kernels e recebe resultados
– O restante do código é executado na CPU
16 III Semana Inverno Geofísica
17. Fonte e Execução
Código Fonte na CPU Execução CPU/GPU
C serial CPU
KernelA<<< nBlk, nTid >>>(args); ...
GPU
C serial CPU
KernelB<<< nBlk, nTid >>>(args); ...
GPU
17 III Semana Inverno Geofísica
18. Extensões a C/C++ em CUDA - Kernels
• Kernels (funções) que serão executadas na GPU
Identificadas pelo prefixo __global__:
__global__ void nome-do-kernel (args)
• O código do Kernel representa uma thread
• Disparo de Kernels na GPU define o número de threads e blocos
nome-do-kernel <<< NBlk, NThread >>> (args);
Blocos Threads por bloco
independentes de (todas as threads em
threads executados um único SM)
em qualquer ordem executadas
(um bloco por SM) simultaneamente
18 III Semana Inverno Geofísica
19. Exemplo: Disparo de soma de vetores de
tamanho N
__global__ void VAdd(float* A, float* B, float* C)
{
int i = threadIdx.x; Threads numeradas
C[i] = A[i] + B[i];
}
de 0 a N-1
int main()
{
.....
dim3 dimBlk (N); // N threads por bloco
dim3 dimGrid (1); // um unico bloco
...
VAdd <<< dimGrid, dimBlk >>> (A_d, B_d, C_d);
....
}
Um bloco de N
threads
19 III Semana Inverno Geofísica
20. Dados que trafegam entre CPU e GPU
• CPU e GPU atuam em espaços de endereçamento distintos
– A CPU não endereça diretamente a memória da GPU e vice versa
– Mas a CPU dispara kernels sobre dados que residem na GPU!!
– Valores podem ser copiados da CPU para a GPU, mas não ponteiros!
• Dados que trafegam entre a CPU e a GPU devem ser declarados e
alocados pela CPU
– CPU contém as duas declarações
– O endereço do dado na GPU deve ser conhecido pela CPU, para ser
usado no “kernel”
• Declarações na CPU:
float * A_h; // vetor A residente no host (CPU)
float * A_d; // vetor A residente no device (GPU)
20 III Semana Inverno Geofísica
21. Alocação de memória na GPU
• Suponha as declarações na CPU:
float * A_h; // vetor A residente no host (CPU)
float * A_d; // vetor A residente no device (GPU)
• O vetor A_h no host é alocado e inicializado na forma usual
• O vetor A_d é alocado na GPU pela função cudaMalloc, invocada
pela CPU
– dealocado pela função cudaFree, invocada pela CPU
• Alocação e dealocação na GPU por run-time invocado pela CPU:
cudaMalloc ((void **) &A_d, size);
cudaFree(A_d);
Reserva a memória size em bytes
e retorna ponteiro
21 para A_d na GPU III Semana Inverno Geofísica
22. Exemplo: Aloca vetor de tamanho N na GPU
int main()
{
size_t size;
float* A_h, B_h, C_h; // no host (CPU)
float* A_d, B_d, C_d; // no device (GPU)
.....
// aloca A, B, C, no device
size = N*sizeof(float);
cudaMalloc( (void **) &A_d, size);
cudaMalloc( (void **) &B_d, size);
cudaMalloc( (void **) &C_d, size);
...
// invoca kernel, passando A, B, C no device e N
VAdd <<< dimGrid, dimBlk >>> (A_d, B_d, C_d, N);
....
}
22 III Semana Inverno Geofísica
23. Trafego de dados entre CPU e GPU
• CPU comanda o tráfego entre as memórias da CPU e
da GPU
– Invocando “run-time”
• Comunicação síncrona
– A invocação na CPU retorna ao término da comunicação
– Há versões assíncronas
• Envio de dados da CPU para a GPU:
– cudaMemcpy (A_d, A_h, size, cudaMemcpyHostToDevice);
• Recepção de dados da GPU na CPU:
– cudaMemcpy (C_h, C_d, size, cudaMemcpyDeviceToHost);
para de tamanho constante simbólica
como em uma atribuição (bytes)
23 III Semana Inverno Geofísica
24. Exemplo: Tráfego de dados
int main()
{
size_t size;
float* A_h, B_h, C_h; // no host (CPU)
float* A_d, B_d, C_d; // no device (GPU)
.....
// envia A e B para o device
size = N*sizeof(float);
cudaMemcpy(A_d, A_h, size, cudaMemcpyHostToDevice);
cudaMemcpy(B_d, B_h, size, cudaMemcpyHostToDevice);
...
// invoca kernel, passando A, B, C no device e N
VAdd <<< dimGrid, dimBlk >>> (A_d, B_d, C_d, N);
...
// recebe C do device
cudaMemcpy(C_h, C_d, size, cudaMemcpyDeviceToHost);
....
}
24 III Semana Inverno Geofísica
25. Sumário
• kernels executados na GPU identificados por __global__
– Cada kernel é uma thread
• Endereços de ponteiros na CPU e na GPU por variáveis distintas
– Declarações distintas
• CPU comanda alocação de memória na GPU
– Invocando cudaMalloc
• CPU copia dados para a memória da GPU
– Invocando cudaMemcpy
• CPU dispara kernels
– Definindo threads por bloco e número de blocos
• CPU recebe resultados
– Invocando cudaMemcpy
25 III Semana Inverno Geofísica
27. Múltiplos Blocos de Threads
• VAdd com vetores de tamanho N, particionado em nBlk
blocos de tamanho tamBlk
– Por exemplo, quando N > 512
0 i N-1
tamBlk tamBlk tamBlk
tamBlk
• Disparo do kernel: threads por
dim3 dB (tamBlk); bloco dB e dG são variáveis locais
dim3 dG (nBlk); à função, invisíveis ao
VAdd <<< dG, dB >>> (A_d, B_d, C_d, N); Kernel
nBlk blocos
27 de threads III Semana Inverno Geofísica
28. Quantidade de Threads
• O Kernel tem que conhecer o número de threads por
bloco e o número de blocos utilizado na invocação
• Há variáveis “build-in”, acessíveis no Kernel
• O número de blocos é gridDim.x
– grid de blocos
• O número de threads por bloco é blockDim.x
– bloco de treads
• No exemplo anterior:
– gridDim.x = nBlk
– blockDim.x = tamBlk
28 III Semana Inverno Geofísica
29. Enumeração das Threads
• Conhecemos a quantidade de threads (gridDim.x * blockDim.x).
Como identificar cada thread?
• Há variáveis “build-in”, acessíveis no Kernel, para o número desta
thread no bloco e o número deste bloco
• As threads em um bloco são enumeradas de 0 a blockDim.x-1 e
identificadas por threadIdx.x
• Os blocos de threads são enumerados de 0 a girdDim.x-1 e
identificados por blockIdx.x
• Logo, o número desta thread (de 0 à N-1) é
blockIdx.x * blockDim.x + threadIdx.x threads neste bloco
quantos blocos de
threads antes deste threads por bloco
29 III Semana Inverno Geofísica
30. Enumerando Threads
• Utilidade: encontrar os índices dos vetores
0 i N-1
blockDim.x blockDim.x blockDim.x
blockIdx.x = 0 blockIdx.x = 1 blockIdx.x =
gridDim.x - 1
• no Kernel:
i = blockIdx.x * blockDim.x + threadIdx.x;
C[i] = A[i] + B[i];
30 III Semana Inverno Geofísica
32. Sincronismo entre Threads de um
Bloco
• As threads de um mesmo bloco sincronizam invocando a função
__syncthreads()
• A função só pode ser invocada dentro de uma função do device
• É responsabilidade do programador evitar condição de corrida entre
as threads pelo uso de __syncthreads()
• Não há mecanismo de sincronismo explícito entre blocos de threads
– Blocos de threads são supostamente independentes
32 III Semana Inverno Geofísica
33. Execução de funções no device
• Na implementação atual, a execução de
funções no device é serializada
• Ou seja, solicitações do host com cudaMalloc,
cudaMemcpy e disparo de kernels são
executados na sequencia recebida, sem overlap
• Logo, invocações consecutivas de um mesmo
kernel são serializadas, impondo sincronismo
entre os blocos de threads de invocações
consecutivas do kernel
33 III Semana Inverno Geofísica
34. Overlap de funções do device no host
• As funções cudaMemcpy, cudaMalloc e cudaFree são sincronas
– Só retornam controle ao host após terminar no device
• Mas invocações de kernel são assincronas
– Retornam controle ao host assim que a solicitação de execução for
colocada no device
• Como a execução no device é sincrona, medidas de tempo no host
devem considerar esse fato
• A função cudaThreadSynchronize(), invocada no host, aguarda o
término de todas as funções em execução no device antes de
retornar controle ao host
• Função necessária para atribuir corretamente tempos medidos no
host a eventos no device (disparo de kernels)
34 III Semana Inverno Geofísica