Interface Hardware-Software
Aumento de Desempenho dos Processadores
Ao longo dos anos, processadores aumentaram o poder de
processamento
– Aumento da frequencia
– Pipeline
– Superescalar
Mais recentemente, o aumento do desempenho foi
alcançado através do suporte em HW à execução paralela
(real) de diferentes tarefas
– Hardware multithreading
– Multicores
Hardware Multi-Threading
Abordagem multi-thread
– Aumentar utilização de recursos de hardware permitindo que
múltiplas threads possam executar virtualmente de forma
simultânea em único processador
– Compartilhamento de unidades funcionais
Objetivos
– Melhor utilização de recursos (cache e unidades de execução
são compartilhadas entre threads)
– Ganho de desempenho (em média 20%)
Simultaneous Multi-Threading (SMT)
SMT
é uma política de multi-threading para processadores
superescalares com escalonamento dinâmico de threads e
instruções
– Escalonamento de instruções de threads diferentes
– Instruções de threads diferentes podem executar
paralelamente desde que haja unidades funcionais livres
– Explora paralelismo ao nível de instrução (
I
nstruction
L
evel
P
arallelism -
ILP
)
– Explora paralelismo ao nível de threads (
T
hread
L
evel
P
arallelism –
TLP
)
Hyper-Threading é um caso de de SMT proposto pela Intel
– Disponível em processadores Xeon, Pentium 4- HT, Atom,
Intel i7
Multicores
Microprocessadores multicores
– Mais de um processador por chip
Requer programação paralela
para uma melhoria efetiva
– Hardware executa múltiplas
instruções paralelamente
Transparente para o
programador
– Difícil de
Programar visando
desempenho
Chapter 7 — Multicores, Multiprocessors, and Clusters — 6
Processamento Paralelo - Hardware e Software
Software sequencial/concorrente pode executar
em hardware serial/paralelo
–
Desafio: fazer uso efetivo de hardware paralelo
Software Sequencial Concorrente Hardware Serial Multiplicação de matrizes em Java no Intel Pentium 4 Windows Vista no Intel Pentium 4 Paralelo Multiplicação de matrizes em Java no AMD Athlon Phenom II Windows Vista no AMD Athlon Phenom II
Programação Paralela
Desenvolver software para executar em HW paralelo
Necessidade de melhoria significativa de desempenho
– Senão é melhor utilizar processador com único core rápido,
pois é mais fácil de escrever o código
Dificuldades
– Particionamento de tarefas (Balanceamento de carga)
– Coordenação
Modelos de Comunicação
Nos computadores atuais, 2 modelos de comunicação
entre núcleos são utilizados:
– Memória Compartilhada (Mais utilizada em arquiteturas
convencionais)
– Passagem de Mensagens (Memória Distribuída)
Chapter 7 — Multicores, Multiprocessors, and Clusters — 9
Memória Compartilhada
Shared memory multiprocessor
Mesmo espaço de memória é compartilhado pelos
diferentes processadores
Comunicação
é
feita
através
de
variáveis
compartilhadas
Comunicação com Memória Compartilhada
Variáveis compartilhadas contêm os dados que são
comunicados entre um processador e outro
Processadores acessam variáveis via loads/stores
Acesso a estas variáveis deve ser controlado
(sincronizado)
– Uso de
locks (semáforos)
– Apenas um processador pode adquirir o lock em um
determinado instante de tempo
Chapter 7 — Multicores, Multiprocessors, and Clusters — 11
Passagem de Mensagens
Message Passing
Processadores
compartilham
dados
enviando
explicitamente os dados (mensagem)
Comunicação é feita através de primitivas de
comunicação (send e receive)
Comunicação com Message Passing
Cada processador tem seu espaço de endereçamento
privado
Processadores mandam mensagens utilizando esquema
parecido ao de acessar um dispositivo de Entrada/Saída
– Processador que aguarda mensagem é interrompido
quando mensagem chega
Sincronização de acesso aos dados é feita pelo próprio
programa
– Programador é responsável para estabelecer os pontos de
comunicação com as primitivas send e receive
OpenMP
OpenMP( Open Multi-Processor) é uma API que dá
suporte ao modelo de programação (comunicação)
paralelo de memória compartilhada
Padronizada pelo consórcio OpenMP ARB(OpenMP
Architecture Review Board)
– AMD, Intel, Cray, HP, Oracle, Nvidia, NEC, etc
– Roda em Linux, Windows, Mac OS X, Solaris, etc
Camadas de SW e OpenMP
Objetivos de OpenMP
Padronização da utilização do modelo de programação de
memória compartilhada para diferentes plataformas
Provimento de uma camada de SW enxuta para programar
plataformas com memória compartilhada
– Poucas diretivas e funções para programação paralela
Facilidade de uso deste modelo de programação
– Permite a paralelização incremental de um código sequencial
com poucas diretivas
Portabilidade
– API em C, C++ e Fortran
– Maioria das plataformas no mercado aceitam Linux e
Windows
Suporte ao Modelo de Memória Compartilhada
Threads tem acesso a mesma memória global compartilhada
Dados podem ser compartilhados ou privados
Dados privados podem ser apenas acessados pelas threads que são as donas Transferência de dados são
transparentes ao programador
Sincronização acontece, mas boa parte é implícita
Paralelismo Suportado por OpenMP
Paralelismo através de threads
– Quantidade de threads não precisa ser igual a quantidade
de cores
Programador consegue explicitar que partes do código
são paralelizados
– Pode explicitar vários níveis de paralelismo
Utiliza o modelo de execução paralela fork-join
– Uma thread master pode criar várias threads para executar
em paralelo
– Quando todas as threads filhas completam as tarefas,
sincronizam e terminam a execução, deixando a thread
master
Modelo Fork e Join
Fonte:
Estrutura Típica de Um Programa OpenMP
Região Paralela
Região
Sequencial
Pode vir seguido de mais regiões paralelas ou
sequenciais
Início
Fim
Thread master (inicial,0) é onde a execução do programa se inicia e termina
Thread master cria um time (conjunto) de threads que vão executar algo em paralelo (fork)
Quando todos os threads de um time chegam ao final da região paralela, todos menos o master terminam (join)
Componentes de OpenMP
Fonte:
Exemplo de OpenMP: Hello World Paralelo
#include <stdio.h>#include <stdlib.h> #include <omp.h> int main(){
int nthreads, tid;
/* Região Paralela, é dado um fork para um time de threads*/ #pragma omp parallel private(nthreads, tid)
{
/* Obtém o número da thread*/ tid = omp_get_thread_num();
printf("Hello World da thread = %d\n", tid); /* Somente o thread master faz isto */
if (tid == 0){
nthreads = omp_get_num_threads();
printf(“Quantidade de threads = %d\n", nthreads); }
} /* No fim é dado um join */ }
Diretiva que indica o começo de uma região paralela
Saída da Execução do Hello World Paralelo
4 threads criadas que
Algumas Considerações Sobre a
Paralelização em OpenMP
A quantidade de threads criadas pode ser configurada
– Variáveis de ambiente, diretivas e chamadas de funções
A ordem de execução das threads é determinada pelo S.O
- A cada execução a ordem pode mudar
Se threads diferentes acessam o mesmo dado, OpenMP não
garante sozinho o acesso na ordem correta do dado
– Programador deve explicitar onde haverá a sincronização
O Que Deve Ser Feito para Usar OpenMP?
No caso de C/C++ ter um compilador que possua a
biblioteca libgomp
– Importante ter também a biblioteca libpthread (Linux) ou
libwinpthread (Windows)
Colocar no código um
#include <omp.h>
Configurar a compilação e a link-edição
Configurando a Compilação no CodeBlocks
gcc deve compilar com esta opção
Configurando a Link-Edição no CodeBlocks
Deve incluir o caminho da biblioteca na link-edição
Diretivas de OpenMP
Boa parte das construções de OpenMP são diretivas de
compilação
– Instruem a forma de execução do código como por exemplo:
paralelização, serialização e sincronização
– Se não colocar a opção de compilação –fopenmp, a diretiva é
simplesmente ignorada
Uma região paralela sob a diretiva é delimitada por chaves
{ }
– Possibilidade de haver outras diretivas dentro das chaves
Forma Geral:
Tipos de Diretivas de OpenMP
Básica de Paralelismo
– parallel
Divisão de dados/tarefas entre threads (work-sharing)
– for (loop)
– section
– single
– task
Sincronização
– barrier
– master
– critical
– atomic
– ordered
Exemplo de Diretiva Parallel : Hello World
#include <stdio.h>#include <stdlib.h> #include <omp.h> int main(){
int nthreads, tid;
#pragma omp parallel private(nthreads, tid) {
tid = omp_get_thread_num();
printf("Hello World da thread = %d\n", tid); if (tid == 0){
nthreads = omp_get_num_threads();
printf(“Quantidade de threads = %d\n", nthreads); }
} }
Cláusula que indica que as threads terão suas próprias cópias de variáveis nthreads e tid
Delimitação da região paralela
Cláusulas OpenMP
Diretivas podem vir seguidas de cláusulas
Cláusulas são usadas para especificar informações
adicionais à diretiva utilizada
Pode-se utilizar várias cláusulas em uma mesma
instância de diretiva
Certas cláusulas só podem ser utilizadas para algumas
diretivas específicas
Exemplo de Diretiva Parallel : Hello World
#include <stdio.h>#include <stdlib.h> #include <omp.h> int main(){
int nthreads, tid;
#pragma omp parallel num_threads(5) private(nthreads, tid) {
tid = omp_get_thread_num();
printf("Hello World da thread = %d\n", tid); if (tid == 0){
nthreads = omp_get_num_threads();
printf(“Quantidade de threads = %d\n", nthreads); }
} }
Cláusula que indica que as threads terão suas próprias cópias de variáveis nthreads e tid
Cláusula que indica a quantidade de threads desejada
Diretivas de Work-Sharing
O processamento é distribuído entre as threads, ou seja o
trabalho é realizado conjuntamente pelas threads
Estas diretivas devem estar presentes dentro de regiões
paralelas
Não podem criar novas threads dentro das áreas
limitadas por estas diretivas
Tipos de Diretivas de OpenMP
Básica de Paralelismo
– parallel
Divisão de dados/tarefas entre threads (work-sharing)
– for (loop)
– section
– single
– task
Sincronização
– barrier
– master
– critical
– atomic
– ordered
Diretiva for
Serve para distribuir as iterações de um laço entre as
threads
– Naturalmente deve existir um laço logo após este tipo de
diretiva
– O laço deve ser do tipo for
A diretiva for não deve vir seguida de chaves { }
Assim como outras diretivas, eles permitem o uso de
cláusulas
Forma Geral:
Exemplo de Diretiva for : Soma Matriz
#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[2][2] = {{1,2},{3,4}}; int B[2][2] = {{5,6},{7,8}}; int C[2][2]; int i,j,tid;#pragma omp parallel private(j,tid) {
#pragma omp for
for(i=0; i < 2; i++) { for(j=0; j < 2; j++){
tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } }
Variável de controle do loop (i) é privada por padrão, haverá uma thread alocada para calcular o valor de cada linha
Importante para que uma thread não interfira no valor de j de outra thread
Combinando parallel e for
São equivalentes
As duas diretivas podem ser combinadas na mesma linha
– Naturalmente deve existir um laço logo após este tipo de
diretiva
Exemplo de parallel e for : Soma Matriz
#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[2][2] = {{1,2},{3,4}}; int B[2][2] = {{5,6},{7,8}}; int C[2][2]; int i,j,tid;#pragma omp parallel for private(j,tid) for(i=0; i < 2; i++) {
for(j=0; j < 2; j++){
tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \
C[%d][%d]=%d\n",tid,i,j,C[i][j]); }
} }
Não precisa de chaves para delimitar área
Tipos de Diretivas de OpenMP
Básica de Paralelismo
– parallel
Divisão de dados/tarefas entre threads (work-sharing)
– for (loop)
– section
– single
– task
Sincronização
– barrier
– master
– critical
– atomic
– ordered
Diretivas sections e section
A diretiva sections serve para listar blocos de código que
serão processados por threads separadas
– Cada bloco deve vir precedido da diretiva section
As diretivas sections e section são delimitadas por chaves
{ }
Caso uma thread seja muito rápida, ela pode executar
mais de uma section
– Ou ainda, se o número de threads definido pelo programador
for menor do que o número de section
Formas Gerais de sections e section
#pragma omp
sections
[claúsula[cláusula]]
{
#pragma omp
section
{código}
#pragma omp
section
{código}
}
Exemplo de sections : Quantidade de Pares
#include <stdio.h> #include <stdlib.h> #include <omp.h> #define N 1000 int main(){ int matriz[N];int i,tid, valorInicial = 1, contaPar = 0; //Inicializando a matriz
for (i = 0; i < N; i++) {
matriz[i] = valorInicial; valorInicial += 3;
Exemplo de sections : Quantidade de Pares
#pragma omp parallel private(i,tid) num_threads(2) {#pragma omp sections reduction(+:contaPar) {
#pragma omp section {
for(i = 0; i < N/2; i++) if (matriz[i] % 2 == 0) contaPar++;
}
#pragma omp section { for(i = N/2; i < N; i++) if (matriz[i] % 2 == 0) contaPar++; } } }
printf("\nQuantidade de pares = %d\n",contaPar); }
Cláusula que garante que os valores das cópias locais de contaPar sejam depois combinadas em um único valor global
Sincronização
Em um ambiente onde o processamento é paralelo, muitas
vezes precisamos de sincronização para garantir a
corretude da execução do código
– Evitar “race conditions”
Algumas situações onde sincronização se faz necessário:
– Acesso a variáveis compartilhadas, onde toda thread deve
enxergar o valor atualizado da variável
– Execução ordenada de trechos de código
OpenMP possui várias diretivas que possibilitam a
sincronização entre threads diferentes
Devem ser usadas com cautela, pois sincronização é
custosa e afeta o desempenho
Formas de Sincronização
As duas formas mais comuns de sincronização são:
Barreira (Barrier)
: cada thread
espera em um ponto de
execução (barreira) até que as
outras threads cheguem
Exclusão Mútua
: Define um
bloco de código (região crítica)
onde apenas uma thread pode
executar em um instante de
tempo
Tipos de Diretivas de OpenMP
Básica de Paralelismo
– parallel
Divisão de dados/tarefas entre threads (work-sharing)
– for (loop)
– section
– single
– task
Sincronização
– barrier
– master
– critical
– atomic
– ordered
Quando Usar Barreiras (Barriers)
Suponham que tivéssemos estes dois
trechos de código e que o loop estivesse
sendo executado em paralelo sobre i
if (num_thread % 2 == 0) {
fator1 = fator1 + 1;
} else {
fator2 = fator2 + 1;
}
for (i = 0; i < N; i++)
a[i] = i * fator2 * fator1;
Usando Barreiras (Barriers)
for (i = 0; i < N; i++)
a[i] = a[i]* fator1 * fator2
Espera!
Todas as threads precisam saber o valor de
fator, antes de proceder ao loop
Barreira
Cada thread espera no ponto da barreira e só
continua quando todas as threads tiverem
atingido este ponto
if (num_thread % 2 == 0) {
fator1 = fator1 + 1;
} else {
fator2 = fator2 + 1;
}
Diretiva barrier
Serve para colocar uma barreira de sincronização
– Cada thread deve esperar as outras quando encontrada esta
diretiva
Forma Geral:
Exemplo de barrier
#include <omp.h>#define N 20 int main(){
int i, num_thread,a[N], fator1 =0,fator2=0;
#pragma omp parallel private(num_thread)shared(fator1,fator2) { num_thread = omp_get_thread_num(); if (num_thread % 2 == 0) fator1++; else fator2++;
#pragma omp barrier #pragma omp for
for(i=0; i < N; i++) {
a[i] = i * fator1 * fator2; printf("a[%d] = %d ",i,a[i]); }
}
Cláusula opcional que declara que fator1 e fator2 são
compartilhadas entre threads
Todas as threads devem chegar neste ponto para proceder ao resto do código
Barreiras Implícitas
Nem sempre precisamos da diretiva
barrier para
especificar uma barreira
Em várias situações há barreiras implícitas consideradas
pelo compilador:
– Fim de regiões paralelas
– Construções work-sharing como
for
possuem barreiras
implícitas ao final do loop, excetuando-se quando se coloca a
cláusula nowait na diretiva
#pragma omp
for
for (i = 0; i < N; i++) {
d[i] = a[i] + b[i];
}
Barreira implícita
Tipos de Diretivas de OpenMP
Básica de Paralelismo
– parallel
Divisão de dados/tarefas entre threads (work-sharing)
– for (loop)
– section
– single
– task
Sincronização
– barrier
– master
– critical
– atomic
– ordered
Diretiva critical
Serve para colocar delimitar uma região crítica
– Só uma thread por vez pode entrar na região crítica
– As demais esperam para entrar (barreira implícita)
Bastante útil para atualização de variáveis compartilhadas
– Garante a consistência de dados
Forma Geral:
#pragma omp
critical
[name]
{código}
name
é opcional, mas regiões críticas com o mesmo nome
são tratados como a mesma região crítica
Exemplo de critical
#include <omp.h>int main() {
int fator1 =0; int tid;
#pragma omp parallel num_threads(5) private(tid) shared(fator1) {
tid = omp_get_thread_num(); #pragma omp critical
{
fator1++;
printf("\nThread %d executando\n",tid); printf("\nValor de fator1 = %d\n",fator1); } } return 0; } Variável compartilhada entre threads Garante a consistência na atualização do dado e fluxo correto de execução do código
Saída do Programa SEM critical
Não gera o resultado esperado
Tipos de Diretivas de OpenMP
Básica de Paralelismo
– parallel
Divisão de dados/tarefas entre threads (work-sharing)
– for (loop)
– section
– single
– task
Sincronização
– barrier
– master
– critical
– atomic
– ordered
Diretivas master e single
Diretiva single especifica que somente uma thread executa
o que está delimitada pela diretiva
– Escolha da thread é aleatória
– Restante das threads esperam enquanto a thread termina a
execução do código delimitado (barreira implícita), exceto
quando há uma cláusula nowait
Diretiva master especifica que somente a thread master
executa o código delimitado pela diretiva
– Outras threads podem continuar a executar o restante do
código (não há barreira implícita)
Formas Gerais:
#pragma omp
master
{código}
#pragma omp
single[cláusulas]
Comportamento de single
Fonte:
Funções da API de OpenMP
OpenMP oferece várias funções que
podem ser chamadas
– Para controlar e ver o status do
ambiente de execução paralela
– Pode-se configurar a quantidade de
threads dinamicamente além de
sincronizar acesso a regiões críticas
Estas funções tem precedência sobre
Sumário das Funções na API
Nome Funcionalidade
omp_set_num_threads Estabelece a quantidade de threads
omp_get_num_threads Retorna a quantidade de threads
omp_get_thread_num Retorna o id da thread
omp_get_num_procs Retorna a quantidade de processadores
omp_get_wtime Retorna o tempo do “wall clock”
omp_get_wtick Retorna o número de segundos entre os
ticks do clock
omp_init_lock Associa um lock (semáforo) com uma
variável
omp_destroy_lock Desassocia um lock de uma variável
omp_set_lock Adquire o semáforo, senão bloqueia
omp_unset_lock Libera o semáforo
Exemplo de Utilização das Funções da API
#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[4][2] = {{1,2},{3,4},{5,6},{7,8}}; int B[4][2] = {{5,6},{7,8},{9,10},{11,12}}; int C[4][2]; int i,j,tid; omp_set_num_threads(2);#pragma omp parallel for private(j) for(i=0; i < 4; i++) {
for(j=0; j < 2; j++){
tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } Estabelece 2 threads Retorna o id da thread
Precedência de Diretivas
#include <stdio.h> #include <stdlib.h> #include <omp.h> int main(){ int A[4][2] = {{1,2},{3,4},{5,6},{7,8}}; int B[4][2] = {{5,6},{7,8},{9,10},{11,12}}; int C[4][2]; int i,j,tid; omp_set_num_threads(2);#pragma omp parallel for private(j) num_threads(4) for(i=0; i < 4; i++) {
for(j=0; j < 2; j++){
tid = omp_get_thread_num(); C[i][j] = A[i][j] + B[i][j]; printf("thread %d calculou \ C[%d][%d]=%d\n",tid,i,j,C[i][j]); } } } Estabelece 2 threads Neste caso, a precedência é da
Funções de Lock
Locks oferecem uma flexibilidade
maior em relação a regiões críticas
(diretiva
critical)
– Permitem que as threads que não
adquiriram o lock façam outras
coisas enquanto esperam
Variáveis de lock devem ser
manipuladas através das funções
apropriadas de OpenMP
Locks devem ser inicializados
– Comportamento indefinido caso
não sejam inicializados
Exemplo de Utilização de Funções de Locks
#include <omp.h> #include <windows.h> int main(){ int tid; omp_lock_t lck; omp_init_lock(&lck); omp_set_num_threads(3);#pragma omp parallel shared(lck) private(tid) {
tid = omp_get_thread_num(); while (!omp_test_lock(&lck)) {
printf("\nThread %d esperando lock \n",tid); Sleep(3);
}
printf("\nThread %d adquiriu lock\n",tid); Sleep(5);
printf("\nThread %d liberou lock\n",tid); omp_unset_lock(&lck);
} }
Lock deve ser inicializado
Testa e adquire o lock
Enquanto não adquire, faz outra coisa
Saída do Programa com Locks
Thread 0 adquiriu o lock, e threads 1 e 2 fazem outra coisa enquanto esperam
Algumas Variáveis de Ambiente OpenMP
Nome Funcionalidade
OMP_NUM_THREADS Estabelece a quantidade de threads
OMP_DYNAMIC Habilita/Desabilita se a quantidade de
threads pode ser ajustada dinamicamente pelo ambiente de execução
OMP_NESTED Habilita/Desabilita o aninhamento de
paralelismo
Existem muitas variáveis de ambiente que podem modificar
o ambiente de execução