










































Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Encontra documentos específicos para os exames da tua universidade
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
PROCESSAMENTO PARALELO, Processamento Paralelo em SMPs, Modelo de programação do OpenMP, TECNOLOGIA MULTICORE
Tipologia: Notas de estudo
1 / 50
Esta página não é visível na pré-visualização
Não perca as partes importantes!











































alternativas para computação de alto desempenho (tecnologia multicore ),
máquinas de memória compartilhada, métricas de desempenho, entre outros.
Na segunda parte, a documentação descreve a sintaxe das diretivas,
funções e variáveis do padrão OpenMP. Com o intuito de esclarecer possíveis
dúvidas, o tutorial também apresenta alguns exemplos que ilustram o uso
desse padrão.
Devido à existência de uma grande variedade de arquiteturas de computadores, inúmeras taxonomias ao longo dos anos foram propostas, porém a classificação mais aceita atualmente nos ambiente de hardware é a conhecida taxonomia de Flynn (Flynn apud Dantas, 1972). Essa classificação leva em consideração o número de instruções executadas em paralelo e o conjunto de dados sob os quais as instruções são submetidas. Dessa forma, na Figura 1 são mostrados os tipos de computadores estabelecidos pela classificação de Flynn.
SISD - Single Instruction Single Data
SIMD - Single Instruction Multiple Data
MISD - Multiple Instruction Single Data
MIMD - Multiple Instruction Multiple Data
Figura 1 – Classificação das arquiteturas de computadores segundo Flynn, 1972
Os computadores denominados com arquitetura MIMD possuem múltiplos núcleos de processamento, cada qual podendo executar instruções independente dos demais. Essas máquinas também podem ser classificadas como multiprocessadores (memória compartilhada) ou multicomputadores (memória distribuída). O primeiro grupo pode também ser denominado de ambiente fortemente acoplado enquanto que o segundo fracamente acoplado (Figura 2). As máquinas denominadas multiprocessadas possuem, como principal característica, o compartilhamento da memória entre todos os processadores.
Figura 2 – Classificação das arquiteturas MIMD
Em arquiteturas de memória compartilhada, outra classificação que também merece destaque é a que define como os processadores acessam a
2.1 – Processamento Paralelo em SMPs
SMP ( Symetric Multiprocessor System ) é uma máquina de arquitetura paralela MIMD com memória compartilhada, constituída por processadores que na maioria das vezes estão conectados à memória por meio de um barramento. Elas possuem arquitetura UMA ( Uniform Memory Access ), isto significa que todos os processadores têm acesso a todas as posições de memória e que o tempo de acesso é o mesmo para todas as posições. Entretanto, vale ressaltar que nem toda memória é compartilhada numa SMP. A velocidade de processamento dos processadores tem crescido de forma bastante rápida, porém as instruções são realizadas sob dados cujo acesso na memória principal do computador não cresce na mesma proporção. Para que esse acesso aos dados aconteça de forma mais rápida, cada processador possui uma memória privada, menor e mais rápida, denominada memória cache. Os dados são então trazidos da memória principal para a memória cache para que o processador possa acessá-los. A memória cache , entretanto, é especifica de cada processador, somente sendo acessada por eles.
Figura 3 – Modelo de compartilhamento de memória de um SMP
O processamento paralelo em SMPs é feito através de programas com múltiplas threads (linhas de execução) ou múltiplos processos que executam simultaneamente, porém de forma independente, e cooperam para resolver um problema através do compartilhamento da memória. O desenvolvimento de programas paralelos com essa característica requer o uso de ferramentas que possibilitem a criação de threads e que implementem mecanismos de sincronização entre elas. As ferramentas de programação paralela mais comuns em SMPs atualmente são aquelas com threading explícito e as baseadas em diretivas de compilação.
Programação paralela com threading explícito - O programador cria explicitamente múltiplas threads dentro de um mesmo processo e divide, também explicitamente, o trabalho a ser realizado pelo programa entre essas threads. Assim como a criação, cabe ao programador gerenciar toda a sincronização das threads. A ferramenta de programação paralela com threading explícito mais utilizada atualmente é a POSIX Threads , que representa uma API ( Application Programming Interface ) do padrão POSIX.
Programação paralela baseada em diretivas – O programador utiliza diretivas de compilação inseridas no código seqüencial para informar ao compilador quais as regiões devem ser paralelizadas. Com base nestas diretivas o compilador gera um código paralelo. Dessa forma é necessário um compilador “especial”, ou seja, capaz de entender as diretivas e gerar o código multi-thread. Pode-se citar o padrão OpenMP como uma interface que especifica um conjunto de diretivas de compilação, funções e variáveis de ambiente para a programação paralela em arquiteturas com memória compartilhada.
3 - PADRÃO OpenMP
O padrão OpenMP é desenvolvido e mantido pelo grupo OpenMP Architecture Review Board (ARB), formado pelos maiores fabricantes de software e hardware do mundo, tais como SUN Microsystems, SGI, IBM, Intel, dentre outros, que, no final de 1997, reuniram esforços para criar um padrão de programação paralela para arquiteturas de memória compartilhada. O Open significa que é padrão e está definido por uma especificação de domínio público e MP são as siglas de Multi Processing. O OpenMP consiste em uma API e um conjunto de diretivas que permite a criação de programas paralelos com compartilhamento de memória através da implementação automática e otimizada de um conjunto de threads. Suas funcionalidade podem atualmente ser utilizadas nas linguagens Fortran 77, Fortran 90, C e C++. A primeira versão desse padrão, o OpenMP 1.0, foi introduzida ao público no final de 1997. Em 2000, para Fortran, e em 2002, para C/C++, foi publicada a versão 2.0. Atualmente, o OpenMP encontra-se na versão 2.5. O OpenMP não é uma linguagem de programação, ele representa um padrão que define como os compiladores devem gerar códigos paralelos através da incorporação, nos programas seqüenciais, de diretivas que indicam como o trabalho será dividido entre os processadores ou cores. Dessa forma, muitas aplicações podem tirar proveito desse padrão com pequenas modificações no código. Mesmo podendo ser usada em máquinas monoprocessadas, o OpenMP foi planejado para satisfazer a implementação em larga escala das arquiteturas SMPs. O sucesso do OpenMP é propagado pelas arquiteturas multicore e multithread e pode ser atribuído a fatores como a robusta estrutura de programação paralela suportada e a facilidade do seu uso.
Diretivas - Consiste em uma linha de código com significado “especial” para o compilador. Por exemplo, nas linguagens C e C++ as diretivas OpenMP são identificadas pelo #pragma omp, enquanto que na linguagem Fortran, as diretivas são identificadas pela sentinela !$omp.
3.2 – Vantagens do uso do OpenMP
▪ Normalmente são feitas poucas alterações no código serial existente ▪ Possui uma robusta estrutura para suporte a programação paralela ▪ Fácil compreensão e uso das diretivas ▪ Suporte a paralelismo aninhado ▪ Possibilita o ajuste dinâmico do número de threads
4 – TECNOLOGIA MULTICORE
Uma das soluções apresentadas e implantadas nos últimos anos para aumentar o poder de processamento das máquinas era o aumento da velocidade de clock. Porém esse caminho possui limitações inerentes, principalmente relacionadas ao super consumo e a emissão de calor. Umas das alternativas para minimizar esses problemas são os processadores Multicore , eles são capazes de prover maior capacidade de processamento com um custo/benefício melhor do que os processadores Single-Core. A tecnologia Multicore (múltiplos núcleos) consiste em colocar duas ou mais unidades de execução ( cores ) no interior de um único “pacote de processador” (um único chip). O sistema operacional trata esses núcleos como se cada um fosse um processador diferente, com seus próprios recursos de execução. Na maioria dos casos, cada unidade possui seu próprio cache e pode processar várias instruções simultaneamente. Adicionar novos núcleos de processamento a um processador possibilita que as instruções de aplicações sejam executadas em paralelo em vez de serialmente, como ocorre em um núcleo único. É importante notar que, para uma total utilização do poder de processamento oferecido pela tecnologia Multicore , as aplicações devem ser escritas de modo a usar intensivamente o conceito de threads. Uma arquitetura Multicore é geralmente um multiprocessador simétrico (SMP) implementado em um único circuito VLSI ( Very Large Scale Integration ). O objetivo é melhorar o paralelismo no nível de threads , ajudando especialmente as aplicações que não conseguem se beneficiar dos processadores superescalares atuais por não possuírem um bom paralelismo no nível de instruções.
5 – MÉTRICAS DE DESEMPENHO
Ao resolvermos um problema num sistema paralelo, o principal interesse é saber o ganho que teremos sobre a implementação serial deste problema. Uma das principais métricas que fornecem essa informação é o fator speed-up S(n) , que representa o ganho de velocidade de processamento de uma aplicação quando executada com n processadores. Quanto maior o speed-up , mais rápido se encontra o código paralelo. A equação que define essa métrica é mostrada abaixo.
n
S n (^) T
TS −Tempo Serial Tn −Tempo Paralelo
Porém, por maior que seja a disponibilidade de processadores a serem utilizados, o fator speed-up é limitado por um valor máximo, decorrente da parcela serial do código, isso é ilustrado pela lei de Amdahl.
5.1 – Lei de Amdahl
Como comentado acima, nem todos os trechos do código são paralelizáveis. Dessa forma pode-se sempre identificar, dentro de um código, uma região que sempre será executada em serial e uma outra que pode ser paralelizada (Figura 6). O aumento do número de processadores apenas influenciará no tempo necessário para executar a região passível de paralelização.
Figura 5 – Ilustração dos trechos serial e paralelo de um código
Observando a ilustração acima, em que n é o número de processadores, podemos combinar estas expressões com a definição do fator speed-up , resultando na expressão conhecida como Lei de Amdahl, que representa o speed-up teórico.
S S n
= onde S n S^ P
lim (^) →∞ =
S −Fração serial do código n −Número de processadores
A idéia da Lei de Amdahl é que existe sempre um limite ao qual a capacidade de ganho pela paralelização estará sujeita. Isso se deve a fatores como entrada e saída, dependência entre os dados e outros fatores intrínsecos à aplicação e à técnica de programação paralela utilizada. Para ilustrar a lei de Amdahl, observemos o exemplo de um pintor que deve executar o trabalho de pintar um conjunto de estacas. As informações relacionadas ao pintor e ao trabalho são ilustradas abaixo (Figura 6).
Preparo da tinta = 30 seg Pintura das estacas = 5 min = 300 seg Tempo para a tinta secar = 30 seg
Figura 6 – Informações no pintor e do trabalho a ser executado
As diretivas do OpenMP são baseadas na diretiva #pragma definida no padrão da linguagem C/C++. Os compiladores que suportam OpenMP em C/C++ possuem uma opção de linha de comando que ativa e permite a interpretação das diretivas do OpenMP.
1.1 – Formato das diretivas
O formato padrão de uma diretiva OpenMP é mostrado a seguir:
#pragma omp nome_da_diretiva [cláusula,...] novalinha
1.2 - Condicional de compilação
Quando um código que contém diretivas do OpenMP é compilado por um compilador que não suporta o OpenMP (ou, no caso dos compiladores que suportam OpenMP, quando a opção de compilação que habilita o OpenMP não é utilizada) este simplesmente ignora as diretivas e compila o programa de forma seqüencial. Entretanto, esse código pode conter alguma instrução específica do OpenMP, como por exemplo, a chamada de funções que retornam informações do ambiente de execução relacionadas ao OpenMP e que estão definidas na <omp.h>. Nesse caso, o compilador não vai encontrar a definição dessas funções e o programa não será compilado. Para tornar possível a compilação de um programa OpenMP tanto na versão paralela quanto na versão seqüencial, pode-se utilizar o condicional de compilação. Nesse caso, as funções do OpenMP ficam sob o controle de uma #ifdef OPENMP e só serão chamadas se essa macro estiver definida. O padrão requer que a macro OPENMP possua um valor do tipo yyyymm, onde yyyy é o ano e mm é o mês quando a versão específica do OpenMP foi lançada. Por exemplo, para o OpenMP 2.5, OPENMP é definido como 200505. A seguir ilustra-se um exemplo da utilização do condicional de compilação.
#ifdef _OPENMP #include <omp.h> #else if #define omp_get_thread_num() 0 #endif ... int id = omp_get_thread_num();
1.3 - Construtor Paralelo
#pragma omp parallel
O construtor paralelo é a diretiva mais importante do OpenMP, uma vez que é o responsável pela indicação da região do código que será executada em paralelo. Se esse construtor não for especificado o programa será executado de forma seqüencial.
SINTAXE: #pragma omp parallel [cláusula,...] novalinha Instrução
As cláusulas que podem ser utilizadas nesse construtor são as descritas abaixo e serão definidas posteriormente.
if (espressão lógica) private (lista de variáveis) shared (lista de variáveis) firstprivate (lista de variáveis) default (shared | none) copyin (lista de variáveis) reduction (operador: lista de variáveis) num_threads (variável inteira)
Quando a thread inicial encontra um construtor paralelo ela cria um grupo de threads que irão executar o código e torna-se a thread mestre desse grupo. Porém, esse construtor não divide o trabalho entre as threads , apenas cria a região paralela. Dentro de uma região paralela, quando as threads encontram outro construtor paralelo, cada uma delas cria um novo grupo de threads e torna-se a thread mestre desse novo grupo. Essas regiões são denominadas regiões paralelas aninhadas e por padrão são executadas de forma seqüencial, ou seja, o novo grupo criado contém apenas uma thread , que e a própria thread mestre do grupo. Uma região paralela é chamada de inativa quando é executada por apenas uma thread e é chamada de ativa quando é executada por várias threads. Pode-se ativar uma região paralela aninhada através da função omp_set_nested (será definida posteriormente). No final de toda região paralela existe uma barreira implícita que faz com que as threads esperem até que todas as threads cheguem naquele ponto. A partir daí, apenas a thread inicial continua a execução do código. Entretanto o openMP especifica uma cláusula que pode ser usada para que o programador decida sobre a existência dessa barreira.
Exemplo
A seguir é ilustrado um trecho de código que realiza a soma de dois vetores. Um construtor paralelo foi colocado no início do bloco de instruções
SINTAXE: #pragma omp for [cláusula,...] for-loop
As cláusulas que podem ser utilizadas no construtor for são as seguintes:
private (lista de variáveis) firstprivate (lista de variáveis) lastprivate (lista de variáveis) reduction (operador: lista de variáveis) schedule (tipo, [,tamanho do chunk]) nowait ordered
Em C/C++, o construtor for só pode ser usado em estruturas de repetição no qual o número de iterações é previamente conhecido e não sofre alteração durante a execução. Ou seja, não se pode utilizar esse construtor em estruturas do tipo while ou do-while. Vale ressaltar que o construtor for implementa SIMD ( Single Instruction Multiple Data ), ou seja, cada thread executa as mesmas instruções sob um conjunto diferente de dados.
Exemplo
O exemplo a seguir mostra o mesmo código do exemplo anterior, porém com um construtor for inserido antes do laço:
#pragma omp parallel { #pragma omp for for (i = 0; i < n; i++) { c[i] = a[i]+b[i];
printf(“Thread %d executa a iteração %d do loop\n”,omp_get_thread_num(),i); } }
Dessa vez, como um construtor de compartilhamento de trabalho foi inserido no código, as iterações do laço serão divididas entre as threads , de
OpenMP é dividir as iterações do laço igualmente e de forma ordenada entre as threads. Por exemplo, se existirem 12 iterações e 3 threads , uma thread fica com as 4 primeiras iterações, a outra com as 4 iterações seguintes e última thread com as últimas 4 iterações. Caso a divisão do número de iterações pelo número de threads não seja exata, o resto da divisão é distribuído igualmente
entre algumas threads. Por exemplo, se existirem 19 iterações e 4 threads , três threads ficarão com 5 iterações e uma thread ficará com as últimas 4 iterações. Porém, pode-se alterar a forma como as iterações são distribuídas entre as threads por meio da cláusula schedule (será definida posteriormente). Além disso, cada thread irá imprimir seu identificador (um valor inteiro retornado pela função omp_get_thread_num()) e a iteração que a mesma está executando. Como o programa será executado de forma paralela, não se pode esperar que as iterações sejam impressas na ordem seqüencial. Dessa forma, ao executar o código acima, pode-se esperar como possível resultado a saída abaixo.
Thread 0 executa a iteração 0 Thread 0 executa a iteração 1 Thread 0 executa a iteração 2 Thread 3 executa a iteração 7 Thread 3 executa a iteração 8 Thread 1 executa a iteração 5 Thread 1 executa a iteração 6 Thread 2 executa a iteração 3 Thread 2 executa a iteração 4
#pragma omp sections
O construtor sections é utilizado para dividir tarefas entre as threads em blocos de código que não possuem iterações. Dessa forma, cada thread irá executar um bloco de código diferente.
SINTAXE: #pragma omp sections [cláusula,...] novalinha { #pragma omp section novalinha instrução #pragma omp section novalinha instrução }
Além do construtor sections, que indica que cada thread irá executar um bloco de instruções diferentes, é necessário incluir no código a diretiva #pragma omp section, que indica qual a instrução que cada thread irá executar. As cláusulas que podem ser utilizadas no construtor sections são as seguintes:
private (lista de variáveis) firstprivate (lista de variáveis) lastprivate (lista de variáveis) reduction (operador: lista de variáveis) nowait